Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react hooks 基础整理与进阶 #19

Open
JTangming opened this issue Jun 4, 2019 · 1 comment
Open

react hooks 基础整理与进阶 #19

JTangming opened this issue Jun 4, 2019 · 1 comment

Comments

@JTangming
Copy link
Owner

JTangming commented Jun 4, 2019

hooks 相关基础性知识总结

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

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

  • 组件间(可能跨层级)如何共享状态?即订阅状态,响应变化等
  • 当数据源发生变化时(如异步事件发生时),如何更新共享状态?

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

hooks 的好处是:

  • 更 FP,更新粒度更细,将 UI 渲染与状态分离
  • hooks 可以引用其它 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 实现原理

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

  • Don’t call Hooks inside loops, conditions, or nested functions
  • Only Call Hooks from React Functions

正如这篇博文 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 JTangming changed the title react hooks进阶与原理 react hooks 基础整理与进阶 Jun 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant