react Hooks 之 useState、useReducer、useEffect 最佳实战方案

2,195

1、理解 useEffect

  • 理解 useEffect 需要抛弃生命周期这条知识点。
  • Hooks 设计思想内没有生命周期概念,我认为它更像监听更新的勾子函数。
  • 页面 render 时,执行 useEffect 函数。

举个栗子:

  1. 获取用户数据并加载。执行1次
  2. 修改姓名并保存。执行n次
const initName = undefined // 姓名

export default () => {
    const [name, setName] = useState(initName)

    useEffect(() => {
        Get.defaultUserInfor()
    }, [false])
    // [false] => 填入 [常量] 只在页面初次加载时执行一次。

    useEffect(() => {
        Post.submitUserInfor(name)
    }, [name])
    // [name] => 监听 [变量] 有更新则执行函数。
    
    return (
        <div>
            <p>
                姓名:
                <input value={name} onChange={val => setName(val) } />
            </p>
        </div>
    )
}

2、繁索的 useState

  • 调用 useState 返回两个值:初始值 和 set函数。
  • 可以在 useState(默认值) 调用时初始一个默认值。
  • 每创建一个变量则调用一次,n 个变量需要调用 n次。

举个栗子【扩展:增加年龄信息】:

  1. 获取用户数据并加载。执行1次
  2. 修改姓名并保存。执行n次
  3. 修改年龄并保存。执行n次
const initName = undefined // 姓名
const initAge = 0 // 年龄

export default () => {
    const [name, setName] = useState(initName)
    const [age, setAge] = useState(initAge)
    
    useEffect(() => {
        Get.defaultUserInfor()
    }, [false])
    // [false] => 填入 [常量] 只在页面初次加载时执行一次。

    useEffect(() => {
        Post.submitUserInfor({ name, age })
    }, [name, age])
    // [name, age] => 监听 [变量一, 变量二] 有更新则执行函数。
    
    return (
        <div>
            <p>
                姓名:
                <input value={name} onChange={val => setName(val) } />
            </p>
            <p>
                年龄:
                <input value={age} onChange={val => setAge(val) } />
            </p>
        </div>
    )
}

举个栗子【扩展:增加保存按钮】:

  1. 获取用户数据并加载。执行1次
  2. 修改姓名、年龄更新页面元素,但不发起请求。执行n次
  3. 增加保存按钮,添加请求事件,保存用户信息。执行n次
const initName = undefined // 姓名
const initAge = 0 // 年龄
const initInfor = { name: undefined, age: 0  } // 用户信息

export default () => {
    const [name, setName] = useState(initName)
    const [age, setAge] = useState(initAge)
    const [infor, setInfor] = useState(initInfor)

    useEffect(() => {
        Get.defaultUserInfor()
    }, [false])
    // [false] => 填入 [常量] 只在页面初次加载时执行一次。

    useEffect(() => {
        Post.submitUserInfor(infor)
    }, [{...infor}])
    // [{...infor}] => 监听 [{...对象变量}] 有更新则执行函数。
    // 解构-防止更新无法被监听。
    
    return (
        <div>
            <p>
                姓名:
                <input value={name} onChange={val => setName(val) } />
            </p>
            <p>
                年龄:
                <input value={age} onChange={val => setAge(val) } />
            </p>
            <p>
                <button onClick={() => { setInfor({ name, age }) }}>保存</buont>
            </p>
        </div>
    )
}

3、优秀的 useReducer

  • useState 基因继承于 useReducer
  • 实际在 useState 内部,也是调用 useReducer 来创建新状态和set函数。
  • useReducer(函数, 初始状态) ,接收 触发函数 和 当前应用state,执行结果并返回最新state。

举个栗子【扩展】:

  1. 获取用户数据并加载。执行1次
  2. 修改姓名、年龄更新页面元素,但不发起请求。执行n次
  3. 点击保存按钮,发送请求保存用户信息。执行n次
// 集中管理 State 状态
// 初始化
const initialState = {
    name: undefined, // 姓名
    age: 0, // 年龄
    infor: {}, // 用户信息
}

// 触发函数,相当于多个 setState 合集
const dispatch = (state, action) => {
    switch(action.type)
    case 'name':
        return { ...state, name: action.payload }
    case 'age':
        return { ...state, age: action.payload }
    case 'infor':
        return { ...state, infor: action.payload }
    default: 
        return state
}

export default () => {
    const [state, dispatch] = useReducer(dispatch, initialState)
    const { name, age, infor } = state

    useEffect(() => {
        Get.defaultUserInfor()
    }, [false])
    // [false] => 填入 [常量] 只在页面初次加载时执行一次。

    useEffect(() => {
        Post.submitUserInfor(infor)
    }, [{...infor}])
    // [{...infor}] => 监听 [{...对象变量}] 有更新则执行函数。
    // 解构-防止更新无法被监听。
    
    return (
        <div>
            <p>
                姓名:
                <input value={name} onChange={val => {
                    dispatch({
                        type: 'name',
                        payload: val
                    })
                }} />
            </p>
            <p>
                年龄:
                <input value={age} onChange={val => {
                    dispatch({
                        type: 'age',
                        payload: val
                    })
                }} />
            </p>
            <p>
                <button onClick={() => {
                    dispatch({
                        type: 'infor',
                        payload: { name, age }
                    })
                }}>保存</buont>
            </p>
        </div>
    )
}

4、更好的方案

  • useReducerClass类 互补使用。
  • Class类 用作管理状态和函数,作用类似 数据模型
  • useReducer 用作为 视图层 注入实例。

举个栗子【扩展】:

  1. 获取用户数据并加载。执行1次
  2. 修改姓名、年龄更新页面元素,但不发起请求。执行n次
  3. 点击保存按钮,发送请求保存用户信息。执行n次
  4. 拆分 state 管理文件 model.js
  5. 视图层 引用 model.jsnew 一个实例。

model.js

export default class Model {
    // 集中管理 State 状态
    // 初始化
    initialState = {
        name: undefined, // 姓名
        age: 0, // 年龄
        infor: {}, // 用户信息
    }
    
    // 触发函数,相当于多个 setState 合集
    // 解构:相同 key键 新的 payload 会替换旧的 state
    reducer(state, payload) {
        return {
            ...state,
            ...payload,
        }
    }
    
    // 赋予,将状态更新器 dispatch 写入该实例中
    // 可以在实例中处理 state 并被 view 层捕获
    assign(dispatch) {
        this.dispatch = dispatch
    }
    
    // 派发,初始状态更新器
    dispatch() { }
    
    // GET请求
    async getUserInfor() {
        const res = await Get.defaultUserInfor()
        if (res) {
            this.dispatch({
                name: res.data.name,
                age: res.data.age,
            })
        }
    }
    
    // POST请求
    async submitUserInfor(params) {
        const res = await Post.submitUserInfor(params)
        res && console.log('保存成功')
    }
}

view.js

import Model from './model.js'
const $model = new Model() // 状态管理

export default () => {
    const [state, dispatch] = useReducer($model.reducer, $model.initialState)
    $model.assign(dispatch)
    const { name, age, infor } = state

    useEffect(() => {
        $model.getUserInfor()
    }, [false])
    // [false] => 填入 [常量] 只在页面初次加载时执行一次。

    useEffect(() => {
        $model.submitUserInfor(infor)
    }, [{...infor}])
    // [{...infor}] => 监听 [{...对象变量}] 有更新则执行函数。
    // 解构-防止更新无法被监听。
    
    return (
        <div>
            <p>
                姓名:
                <input value={name} onChange={val => {
                    dispatch({ name: val })
                }} />
            </p>
            <p>
                年龄:
                <input value={age} onChange={val => {
                    dispatch({ age: val })
                }} />
            </p>
            <p>
                <button onClick={() => {
                    dispatch({
                        infor: { name, age }
                    })
                }}>保存</buont>
            </p>
        </div>
    )
}

结语

学习 hook 并上手项目摸索的方案,依据个人代码习惯和思维进行目录的简单构建。

当然也还有很多值得优化的地方,比如:用 Class类 的继承 或 @装饰器 ,抽离 触发派发赋予 3个函数方法;也可以完全脱离 dispatch视图层 时实监听 model 层的变化。

自己也在不断学习中,有错误处或不合理处要积极指正哦!哈哈哈~