React Hooks之useState/useReducer(手写useState)

手写useState:

import ReactDOM from "react-dom";
import React from "react";

let _state = []
let index = 0
const myUseState = (initialState) => {
    const currentIndex = index
    _state[currentIndex] = _state[currentIndex] === undefined ? initialState : _state[currentIndex]
    const setState = (newState) => {
        _state[currentIndex] = newState
        render()
    }
    index ++
    return [_state[currentIndex], setState]
}

const render = () => {
    index = 0
    ReactDOM.render( <App/>,document.getElementById('root') );
}

const App = () => {
    const [n, setN] = myUseState(0)
    const [m, setM] = myUseState(0)
    return <>
            <div>
                n:{n}
                <button onClick={() => {setN(n + 1)}}>n+1</button>
            </div>
            <div>
                m:{m}
                <button onClick={() => {setM(m + 1)}}>m+1</button>
            </div>
        </>
}
export default App

总结:

  • 每个函数组件对应一个React节点
  • 每个节点保存着state和index
  • useState会读取state[index]
  • index由useState出现的顺序决定
  • useSate有固定的出场顺序:
    例如第一次渲染时,n是第一个,m是第二个,k是第三个;
    那么第二次渲染时,必须保证完全一致的顺序;
    所以useState不能放在条件判断里面去决定是否渲染。
  • setState会修改state,并触发更新
  • state每一次的改变都是一个新的state (但vue3没有产生新的state) =>useRef就是一个状态贯穿始终

以上解释是做了简化,React节点为FiberNode,_state的名称为memorizedState,index的实现则用到了链。

一个小例来说明产生了新的n:

const App = () => {
    const [n, setN] = React.useState(0)
    const onClickN = () => {
        setTimeout(() => {
            console.log(n);
        }, 3000)
    }
    return <div>
        <div>n:{n}</div>
        <button onClick={()=>setN(n+1)}>n+1</button>
        <button onClick={onClickN}>点击,3秒后打印出n</button>
    </div>
}

以上代码,两个按钮点击的先后顺序不用会打印出不同的结果哦

  • 先点击'n+1'按钮,后点击'3秒后打印出n'按钮,3秒后打印出1,因为这时UI已经更新,打印出新的n
  • 先点击'3秒后打印出n'按钮,后点击'n+1'按钮,3秒后打印出0,虽然这时UI已经更新,产生新的n:1,但指定打印的是旧的n,setN后得到的是新的n,新的n和旧的n是同时存在的。

setState注意事项

  1. state为对象时,不可局部更新,用以下方法解决:
function App(){
    const [user, setUser] = React.useState({name:'fanlelee',age:18})
    const onClick = ()=>{
        setUser({     //给一个全新的对象
            ...user, //先复制一份原来的数据
            name:'Lisa'
        })
        
        /*** 错误写法!!***/
        user.name='Lisa'
        setUser(user) //因为对象地址没有改变,React就认为数据没有变化
        /*******/
    }
    return (
        <div>
            <button onClick={onClick}>Click</button>
        </div>
    )
}

因为setSate不会帮我们合并属性,另外useReducer也不会合并属性!!!

  1. state如果是一个对象,setState()里面必须是一个新的对象,因为setState如果对象地址不变,那么React就认为数据没有变化。

  2. setState接受函数(多次操作setState)

function App(){
    const [n, setN] = React.useState(0)
    const onClick = ()=>{
    
        /*** 错误写法!!***/
        setN(n+1)
        setN(n+1) //n不会+2,只会+1
        /*******/
        
        //setN接受函数:
        setN(x=>x+1)
        setN(y=>y+1)
        //以上,得到的结果是+2
    }
    return (
        <div>
            n:{n}
            <button onClick={onClick}>Click</button>
        </div>
    )
}

useState注意事项

  • useState()里面的初始值可以是一个函数,该函数返回一个对象
    因为有时候对象里面的值比较复杂,需要计算得到,把它写成一个“返回对象的函数”可以减少计算过程;
    例:
useState({name:'Lisa',age:9+9}) //js引擎会去解析这个对象
useState(()=>({name:'Lisa',age:9+9})) //js引擎是不会立即执行函数的
  • 当你发现用了多个useState的变量应该放在一个对象里面,这个时候可能用useReducer更合适:
    a. 所有在useState里面的规则在useReducer里面是一样的;
    b. 总的来说useReducer就是useState的升级版;
    c. useReducer就是把参数的操作集中写在了reducer里面;
    d. useReducer的好处就是用来践行Flux/Redux的思想。
const initialUser = {
    name: 'fanlelee',
    age: 18
};

function reducer(state, action) {
    switch (action.type) {
        case 'addAge':
            return state = {...state, age: state.age + 1};
        case 'changeName':
            return state = {...state, ...action.data};
        case 'reset':
            return initialUser;
        default :
            throw new Error('unknown type')
    }
}

function App() {
    const [user, dispatch] = useReducer(reducer, initialUser);
    return (
        <div>
            <div>
                姓名:
                <input type="text" value={user.name}
                       onChange={(e) => {
                           dispatch({
                                type: 'changeName', 
                                data: {name: e.target.value}})
                       }}
                />
            </div>
            <div>
                年龄:{user.age}
                <button onClick={(e) => { dispatch({type: 'addAge'}) }}>
                    +1
                </button>
            </div>
            <div>
                <button onClick={(e) => { dispatch({type: 'reset'})}}>
                    重置
                </button>
            </div>
            {JSON.stringify(user)}
        </div>
    );
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容