JTangming / blog

My repository on GitHub.
Other
53 stars 0 forks source link

react hooks 基础整理与进阶 #19

Open JTangming opened 5 years ago

JTangming commented 5 years ago

hooks 相关基础性知识总结

React Hooks 是 React 16.7.0-alpha 版本推出的新特性,文章一下都简称 hooks。

hooks 与 Redux/mobx 解决的不是同一类问题,Redux/mobx 解决的状态共享问题:

hooks 其根本不是解决状态共享的问题,解决的问题是如何抽离、复用与状态相关的逻辑,是继 render-propshigher-order components 之后的第三种状态共享方案,不会产生如类组件 JSX 嵌套地狱问题。

hooks 的好处是:

常用内置 hooks

useState

在 hooks 之前,开发组件主要是类组件和函数组件,函数组件没有 state,只能简单的将 props 映射成 view。useState 让开发者能够在函数组件里面拥有 state 并能修改 state。一个简单的例子:

import React, { useState } from 'react';
function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect

useEffect 是用于处理各种状态变化造成的副作用,也就是说只有在特定的时刻,才会执行的逻辑。

hooks 的特点是方便 connect 一切,包括通 HTTP 获取数据流、异步事件监听与派发等都可以使用之,利用 useEffect 也可以代替一些生命周期,如事件订阅与销毁。useEffect 就是用来处理这些功能可能产生的副作用的,以下通过 Http 获取数据填充模板来说明。

function App () {
  const [data, setData] = useState({ xx: [] });
  useEffect(async () => {
    const result = await fetch(xxx);
    setData(result.data);
  }, []);
  return (
    <ul>
      {data.xx.map(item => (
        // ...
      ))}
    </ul>
  );
}

通过 useEffect 来处理副作用,传递一个空数组作为 useEffect 的第二个参数,这样就能避免在组件更新执行 useEffect 而造成的死循环。

useEffect 的第二个参数可用于定义其依赖的所有变量。如果其中一个变量发生变化,则 useEffect 会再次运行。如果包含变量的数组为空,则在更新组件时 useEffect 不会再执行,因为它不会监听任何变量的变更。

useReducer

利用 hooks 来创建一个 useReducer,代码如下:

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);
  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }
  return [state, dispatch];
}

一个使用 useReducer 的例子:

function todosReducer(state: ImmutableType<State> = initialState, action: Action) {
  switch (action.type) {
    case XXX:
      return nextState; // 伪代码
    default:
      return state;
  }
}
// action useTodos
function useTodos() {
  const [todos, dispatch] = useReducer(todosReducer, []);
  return [todos,  dispatch({ type: "add", text })];
}

不过需要注意的是,每次使用 useReducer 都是局部的数据状态管理,不会像 redux 一样可以全局持久化,如果要维持一个全局状态,可以搭配 useContext 一起使用。一个比较好的最佳实践可以参考 redux-react-hook

hooks 实现原理

开发者需要遵循的两条规则:

正如这篇博文 React hooks: not magic, just arrays 所描述的那样,hooks 就是通过一张类链表的关系来维持 state 和 setters 的关系,即初始化的时候,创建两个数组 state 和 setters,cursor 设置为 0,第一次调用 useState 的时候,会将 setXX 函数放入到 setters 数组中,把 useState 的初始化数据放入到 state 数组中。以此类推,需要注意的是,每一次重新渲染,cursor 都会重置为 0。

可以参考以上原文的 useState 对应数组变化的流程图:

有了以上的基础,我们更进一步,useState 本身是无状态的,那么它是如何获取前一次的 state 做 diff 的呢?

在执行函数组件的 useState 的时候,在对应的 Fiber 对象上 memoizedState 记录对应关系,返回 useState 执行的结果,而 next 指向的是下一次 useState 对应的 hook 对象,即:

hook1 => Fiber.memoizedState
state1 === hook1.memoizedState
hook1.next => hook2
state2 === hook2.memoizedState
hook2.next => hook3
state3 === hook2.memoizedState

这也就能和以上流程对应起来了,确实是一个 just Array 的关系。现在看看开头标明的,hooks 不能使用嵌套,循环和条件判断中,正是因为 state 和 setters 的一一对应关系,如上三个 hooks 的例子,如果下次执行 useState 的时候,因为如某个判断条件导致某个 useState 没执行,那么这个一一对应关系就乱套了。

那么最后,执行 useState 后如何更新 state 并执行 render 呢,和前面提到的 setState 一样的,可以参考关于 React setState,你了解多少?

JTangming commented 5 years ago

可以精读的文章: