Hooks原理解析与自定义hooks

11 篇文章 0 订阅
5 篇文章 0 订阅

写在前面

对于使用hook大家都有一定的经验,能够很好地使用React强大的能力。我正好深入学习了一下,可能理解的不对,还请指正。

什么是Hook

这个时候往往会有好几个版本的定义。

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 ——这使得你不使用 class 也能使用 React。

官网如是说。

从阮老师的博客中,也提到:

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks
就是那些钩子。 你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。

其实就是为了加强函数组件,让开发在使用函数组件的时候,能够摆脱类的束缚,但是又能够使用到类组件中的state以及生命周期相关的东西。

用法

useState

最简单的用法就是:

function Counter() {
  var [count, setCount] = useState(0);

  return (
    <div>
      <div>{count}</div>
      <Button onClick={() => { setCount(count + 1); }}>
        点击
      </Button>
    </div>
  );
}
自我实现

根据其用法,可以使用闭包自定义一个uesState

var _state; // 把 state 存储在外面

function useState(initialValue) {
  _state = _state || initialValue; // 如果没有 _state,说明是第一次执行,把 initialValue 复制给它
  function setState(newState) {
    _state = newState;
    render();
  }
  return [_state, setState];
}

useEffect

useEffect最常见的用法就是初始化用于获取数据:

useEffect(() => {
    getData();
 }, []);

特点:

  1. 有两个参数 callback 和 dependencies 数组
  2. 如果 dependencies 不存在,那么 callback 每次 render 都会执行
  3. 如果 dependencies 存在,只有当它发生了变化, callback 才会执行
自我实现:
let _deps; // _deps 记录 useEffect 上一次的 依赖

function useEffect(callback, depArray) {
  const hasNoDeps = !depArray; // 如果 dependencies 不存在
  const hasChangedDeps = _deps
    ? !depArray.every((el, i) => el === _deps[i]) // 两次的 dependencies 是否完全相等
    : true;
  /* 如果 dependencies 不存在,或者 dependencies 有变化*/
  if (hasNoDeps || hasChangedDeps) {
    callback();
    _deps = depArray;
  }
}

Not Magic, just Arrays

前面利用闭包存储状态,同样,多个useState也会存储在一个专门的全局变量中,而如文中提到的,仅仅是用Array实现而已。

  • 第一次渲染时候,根据 useState 顺序,逐个声明 state 并且将其放入全局 Array 中。每次声明 state,都要将 cursor 增加 1。
  • 更新 state,触发再次渲染的时候。cursor 被重置为 0。按照 useState 的声明顺序,依次拿出最新的 state 的值,视图更新。

以这段代码为例:

function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi");
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}
1) 初始化

创建两个空的数组:settersstate
设置cursor为0
初始化渲染

2) 第一次渲染

第一次运行时,每次useState()调用,都会将setter函数(绑定到游标位置)推到setter数组上,然后将某些状态推到state数组上。
第一次渲染

3) 接下来的渲染

随后的每次渲染都会重置游标,然后从每个数组中读取这些值。
随后的渲染

4) 事件处理

每个setter都有一个指向其光标位置的引用,因此通过触发对任何setter的调用,它将更改state数组中该位置的状态值。
事件处理

实现

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;

function createSetter(cursor) {
  return function setterWithCursor(newVal) {
    state[cursor] = newVal;
  };
}

// This is the pseudocode for the useState helper
export function useState(initVal) {
  if (firstRun) {
    state.push(initVal);
    setters.push(createSetter(cursor));
    firstRun = false;
  }

  const setter = setters[cursor];
  const value = state[cursor];

  cursor++;
  return [value, setter];
}

// Our component code that uses hooks
function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
  const [lastName, setLastName] = useState("Yardley"); // cursor: 1

  return (
    <div>
      <Button onClick={() => setFirstName("Richard")}>Richard</Button>
      <Button onClick={() => setFirstName("Fred")}>Fred</Button>
    </div>
  );
}

// This is sort of simulating Reacts rendering cycle
function MyComponent() {
  cursor = 0; // resetting the cursor
  return <RenderFunctionComponent />; // render
}

console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']

// click the 'Fred' button

console.log(state); // After-click: ['Fred', 'Yardley']

那么,React文档中提到hook时,有两个额外的规则又是为什么呢?

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中。)

那假设这么使用:

let firstRender = true;

function RenderFunctionComponent() {
  let initName;
  
  if(firstRender){
    [initName] = useState("Rudi");
    firstRender = false;
  }
  const [firstName, setFirstName] = useState(initName);
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}

上述代码中,在条件语句中使用useState,看看结果:

1) 第一次渲染

错误使用时的第一次渲染

2) 第二次渲染

之后的渲染

可以看出来,第二次渲染时,绕过了条件语句里的内容,因此state对应不上,导致了错误的结果。

自定义Hook

讲了这么多,那实际工作中怎么自定义hook呢?
首先可以参考官网的文档,https://zh-hans.reactjs.org/docs/hooks-overview.html#building-your-own-hooks
这边我再班门弄斧,简单说明一下自我的理解。
以一个简单的产品列表为例:

const useProduct = ({
  offset,
  limit,
  searchValues,
}) => {
  const [products, setProducts] = useState({
    items: [],
    total: 0,
  });

  const fetchProducts = useCallback(async () => {
      const result = await getProducts({
        $offset: offset,
        $limit: limit,
        searchValues,
      });
      setProducts(result);
    
  }, [
    offset,
    limit,
    searchValues,
  ]);

  const removeProduct = useCallback(
    async (productId) => {
      await deleteProduct(productId);
      fetchProducts();
    },
    [fetchProducts],
  );

  useEffect(() => {
    fetchProducts();
  }, [fetchProducts]);

  return [
    products,
    {
      fetchProducts,
      removeProduct,
    },
  ];
};

可以理解为数据的操作都在hook里进行,而外部只关心自己想要的。我只要数据列表,获取产品钩子(可能并不需要,可通过参数变更从而触发重新获取数据)、删除产品钩子,此外有可能还会有修改产品的。

代码可能有所缺陷,还请指出。

结束语
越学习越发现自己的不足。目前刚开始用React,自知算不上了解,还有很深的水,也希望能够有机会多深入学习学习。

参考链接:
Deep dive: How do React hooks really work?
React hooks: not magic, just arrays
useEffect 完整指南
React Hooks 原理
React Hooks 入门教程

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GJWeigege

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值