现在React 16+已经是主流并且挺成熟了,如果还不知道啥是hooks,没用过hooks,或者只知道它是钩子,已经很难在面试时说服面试官选择自己了。
所以很有必要搞清楚,到底啥玩意是钩子,它解决了什么,到底哪里好,它们都是如何实现的?
带着这样的问题,一起学习一下React常用 的hooks。

什么时候使用hooks,它有什么好处?

  • 类组件可复用性差 (高阶组件复用,多层复用)
  • 类组件性能稍差 (需要维护类实例)
  • 生命周期管理起来麻烦(例:componentWillMount,不保证只调用一次)
  • 函数组件+ hooks可以实现类组件的功能。
  • useState

    let [value, setValue] = useState(initValue)
    //value是当前状态
    //setValue是变更状态的函数
    //useState就是一个hooks
    //initValue就是设置的初始状态
    

    使用useState实现一个简单的计数功能:

    function Counter(){
      const [number, setNumber] = useState(0);
      return (
          <p>计数:{number}</p>
          <button onClick={()=> setNumber(number+1)}>+</button>
          <button onClick={()=> setNumber(number-1)}>-</button>
    

    代码参考:useState函数组件计数
    根据上面的函数组件,number是维护计数的状态 ,setNumber用来触发更新状态。
    useState返回的初始值以及该变更方法,每次变更后,还重新渲染了组件:

    * 传参:initialState 初始值 返回: 值,以及更新方法[state, setState] //维护一个备忘状态 let memorizedState; function useState(initialState) { memorizedState = memorizedState || initialState; function setState(newState) { memorizedState = newState; render(); return [memorizedState, setState];

    代码参考:useState_easy1

    但是呢,上面这个实现的useState,存在问题,就是如果再初始化一个名称,也就是多次调用useState,就会状态混乱。因为变量memorizedState就是七秒钟记忆的鱼,只维护了上次的状态,多个状态就蒙了。
    所以 ,我们考虑先用数组维护一下各自的状态,每次通过索引获取对应的,把上面的改一下:

    //维护一个备忘状态
    let memorizedState = [];
    let index = 0; //每次useState初始化时,传入这个索引
    function useState(initialState) {
      memorizedState[index] = memorizedState[index] || initialState;
      let currentIndex = index
      function setState(newState) {
        memorizedState[currentIndex] = newState;
        index = 0; //渲染前要归0
        render();
      return [memorizedState[index++], setState]; //下次要加1
    

    代码参考:useState

    useEffect

    副作用是一个不得不提的钩子,在中文里副作用似乎不是啥好事,但是在hooks中,副作用的功能可是相当强大,它可能替代类组件中的生命周期,如下图:

    //callback:回调函数,deps依赖项 function useEffect(callback, deps){ if(!deps) return callback(); //如果没有依赖项,直接执行 let changed = lastDep?!deps.every((item, idx) => item === lastDep[idex]):true; if(changed){//如果依赖项变更了,会执行回调 callback(); lastDep = deps;

    参考代码:实现useEffect-1
    但是,这个方法有点问题,如果依赖项相同,多使用几次useEffect(()=>{}),便不会触发回调,也就是索引问题。
    可以试一试:

     useEffect(()=>{
        console.log('number2', number);
      }, [number]);
      useEffect(()=>{
        console.log('number2-1', number);
      }, [number]);
    

    useEffect正常是两个都会执行并打印,但是上面写的由于共用一个lastDeps变更,所以才只执行了一次。
    那我们按照前面useState借助数组的方法来优化一下。

    //...
    function useEffect(callback, deps){
        if(!deps) { 
          index++;
          return callback()
        }; //如果没有依赖项,直接执行
      let lastDeps = memorizedState[index];
        let changed = lastDeps?!deps.every((item, idx) => item === lastDeps[idx]):true;
      if(changed){
        callback();
        lastDeps = deps;
        memorizedState[index] = deps;
      index++;
    

    代码参考:useState&&useEffect

    useReducer

    useReducer是个高级hook,如果了解过redux,那就会发现useReducer跟redux里的reducer超级像。
    它也可以进行复杂状态的维护,我们先使用useReducer实现一下上面的计数器功能:

    let initalArg = 0;
    * 处理并返回新状态
    function reducer(state, action){
        switch(action.type){
          case 'add':return { number: state.number+1};
          case 'minus':return { number:state.number-1};
          default:
            return state;
            break;
    * 初始化状态的方法
    function init(initalArg){
        return { number: initalArg};
    function Counter(){
      let [state, dispatch] = useReducer(reducer, initalArg, init);
      return (
          <p>计数器:{state.number}</p>
          <button onClick={()=> dispatch({type:'add'}) } >+</button>
          <button onClick={()=> dispatch({type:'minus'}) }>-</button>
    

    代码参考:useReducer计数器

    逻辑图如下:

    function useReducer(reducer, initialArgs,init){ let initState; if(typeof init !== 'undefined'){ initState = init(initialArgs); }else { initState = initialArgs; memoizedState = memoizedState || initState; function dispatch(action){ memoizedState = reducer(memoizedState, action); render(); //重新渲染 return [memoizedState, dispatch];

    代码参考:实现useReducer-1

    变更多个状态,比如名称也修改一下,需要在dispatch派发事件传payload对象。

    //reducer中追加一个editName的行为
    function reducer(state, action){
        switch(action.type){
          case 'add':return { ...state, number: state.number+1};
          case 'minus':return {...state, number:state.number-1};
          case 'editName': 
            let { name } = action.payload;
            return {...state, name }; 
          default:
            return state;
            break;
    //初始化状态时name
    * 初始化状态的方法
    function init(initalArg){
        return { number: initalArg, name: '计数器'};
    //页面中加dom
     <button onClick={()=> dispatch({type:'editName', payload:{
              name:'计数器'+ Date.now()
            }})}>变更名称:</button>
    

    代码参考:useReducer-2

    使用useReducer实现useState:

    function useState(initState){
      //useReducer参数:reducer, initValue,init(可选)
      return useReducer((oldState, newState) =>{
        return newState;
      },initState);
    

    代码参考:使用useReducer实现useState

    先理解一下链表结构:

    let workInProgressHook = firstWorkInProgressHook; //当前工作中的hook function useState(initialState){ //判断如果没有就初始化一个,有的话,当前指针向后移。 let currentHook = workInProgressHook.next?workInProgressHook.next:{ memoizedState:null, next:null, function setState(newState){ currentHook.memoizedState = newState; workInProgressHook = firstWorkInProgressHook; //重新渲染时,重置work render(); //重新渲染 if(workInProgressHook.next){ //工作中的hook如果存在next workInProgressHook = workInProgressHook.next; //往后移位。 }else { workInProgressHook.next = currentHook; //如果当前工作的没有下一个要执行钩子,则给它挂上。 workInProgressHook = currentHook