前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Reducer:让代码更灵活&简洁

Reducer:让代码更灵活&简洁

作者头像
奋飛
发布2024-05-25 19:35:43
840
发布2024-05-25 19:35:43
举报
文章被收录于专栏:Super 前端Super 前端

解决问题: 🌿 分散的 state,导致代码扩展&维护困难; 🌾 对于输入值的控制/转换等(如希望限制age在1-120之间)

React 表单场景的开发中,往往需要维护众多 state (如,表单数据),过多的 state 会导致源代码冗长,可读性比较差;且未来增删改字段,需要修改的地方也较多,难以维护。

举例:下述表单有三个字段,需要提交给服务

常规写法

针对每个字段封装单独的 state 管理。

代码语言:javascript
复制
export default () => {
    const [name, setName] = useState('');
    const [age, setAge] = useState(null);
    const [address, setAddress] = useState('');

    return (
        <div>
            <label htmlFor="name">name</label>
            <input type="text" value={name} onChange={(e) => setName(e.target.value)} /><br/>
            <label htmlFor="age">age</label>
            <input type="number" value={age} onChange={(e) => setAge(e.target.value)} /><br/>
            <label htmlFor="address">address</label>
            <input type="text" value={address} onChange={(e) => setAddress(e.target.value)} /><br/>
            {name}-{age}-{address}
        </div>
    )
}

每个表单项有自己的 state 及对应的修改值的方法,如果未来对某个表单项进行增删改,与 state 配套的 DOM 需要同步处理。

统一 state

将字段封装到一个 state 管理。

代码语言:javascript
复制
export default () => {
    const  [personalInfo, setPersonalInfo] = useState({
        name: '',
        age: null,
        address: ''
    })

    return (
        <div>
            {
                Object.keys(personalInfo).map((key) => {
                    return <div key={key}>
                        <label htmlFor={key}>{key}</label>
                        <input type="text" value={personalInfo[key]} onChange={(e) => setPersonalInfo({...personalInfo, [key]: e.target.value})} />
                    </div>
                 })
            }
            {personalInfo.name}-{personalInfo.age}-{personalInfo.address}
        </div>
    )
}

这种方式可以精简代码,但需要注意不能直接改变原对象,需要通过 ==...personalInfo来处理。

如果需要对某个值从“数据”层面(如age只允许1-120)做判断,使用这种方式无法完成。

当然,首先要在UI中提供验证

reducer 封装

使用 reducer 进行封装管理。如果对 reducer 还不熟悉,可以跳转到文章尾部,查看相关介绍(来自官网)。

代码语言:javascript
复制
export default () => {
    const [personalInfo, setPersonalInfo] = useReducer((state, next) => {
        return {...state, ...next};
    }, {name: '', age: null, address: ''})

    return (
        <div>
            {
                Object.keys(personalInfo).map((key) => {
                    return <div key={key}>
                        <label htmlFor={key}>{key}</label>
                        <input type="text" value={personalInfo[key]} onChange={(e) => setPersonalInfo({[key]: e.target.value})} />
                    </div>
                 })
            }
            {personalInfo.name}-{personalInfo.age}-{personalInfo.address}
        </div>
    )
}

以一种集中的方式且可以确保 state 总是有效的。并提供了一个控制 state 的函数能力(可以控制无效的数据,避免无效的渲染)。

如:上述提到的,希望age控制在1-120之间

代码语言:javascript
复制
const [personalInfo, setPersonalInfo] = useReducer((state, next) => {
    const newState = {...state, ...next};
    if (newState.age > 120) newState.age = 120;
    if (newState.age < 1) newState.age = 1;
    return newState;
}, {name: '', age: null, address: ''})

当然,这里也可以使用官方推荐的方式:

代码语言:javascript
复制
const [personalInfo, setPersonalInfo] = useReducer((state, action) => {
    const newState = {...state};
  	switch (action.type) {
        case 'updateAge': {
           newState.age = action.age;
           if (newState.age > 120) newState.age = 120;
    			 if (newState.age < 1) newState.age = 1;
           break;
        }
    }
    return newState;
}, {name: '', age: null, address: ''})

其调用 onChange={(e) => setPersonalInfo({type: 'updateAge', age: e.target.value})}

‼️ useReducer 的状态值(state)是不可变的,不能更改!

代码语言:javascript
复制
useReducer((state, next) => {
  // ❌ 改变现存 state 对象
  state.age = next.age;
  return state
  
  // ✔️ 创建新的对象
	return {...state, ...next};
}, {name: '', age: null, address: ''})

这个问题可以通过 Immer 解决。

useReducer

对于拥有许多状态更新逻辑的组件来说,过于分散的事件处理程序可能会令人不知所措。 对于这种情况,可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 reducer

useReducer 是一个 React Hook,允许向组件里面添加一个 reducer

代码语言:javascript
复制
const [state, dispatch] = useReducer(reducer, initialArg, init?) 

参数:

  • reducer:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。
  • initialArg:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
  • [可选参数] init:用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg

返回值:

  • state: 初次渲染时,它是 init(initialArg)initialArg (如果没有 init 函数)。
  • dispatch 函数:用于更新 state 并触发组件的重新渲染。
入参:reducer
代码语言:javascript
复制
function myReducer (state, action) {
  // 给 React 返回更新后的状态
  return {...}
}
  1. 声明当前状态(state)作为第一个参数;
  2. 声明 action 对象作为第二个参数;
  3. reducer 返回 下一个 状态(React 会将旧的状态设置为这个最新的状态「返回值 state」)。
返回值:dispatch

dispatch 函数允许更新 state 并触发组件的重新渲染。它需要传入一个 action 作为参数:

代码语言:javascript
复制
dispatch({ type: 'incremented_age' });

可以是任意类型的值。通常来说 action 是一个对象,其中 type 属性标识类型,其它属性携带额外信息。

  • dispatch 函数 是为下一次渲染而更新 state。因此在调用 dispatch 函数后读取 state 并不会拿到更新后的值,也就是说只能获取到调用前的值。
  • 如果你提供的新值与当前的 state 相同(使用 Object.is 比较),React 会 跳过组件和子组件的重新渲染,这是一种优化手段。虽然在跳过重新渲染前 React 可能会调用你的组件,但是这不应该影响你的代码。
  • React 会批量更新 state。state 会在 所有事件函数执行完毕 并且已经调用过它的 set 函数后进行更新,这可以防止在一个事件中多次进行重新渲染。如果在访问 DOM 等极少数情况下需要强制 React 提前更新,可以使用 flushSync。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常规写法
  • 统一 state
  • reducer 封装
  • useReducer
    • 入参:reducer
      • 返回值:dispatch
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档