React Hooks 深入系列

栏目: 服务器 · 发布时间: 6年前

内容简介:本文基于近段时间对 hooks 碎片化的理解作一次简单梳理,在 class 已经融入 React 生态的节点下, React 推出的 Hooks 具有如下优势:

React Hooks 深入系列

本文基于近段时间对 hooks 碎片化的理解作一次简单梳理, 个人博客 。同时欢迎关注基于 hooks 构建的 UI 组件库 —— snake-design

在 class 已经融入 React 生态的节点下, React 推出的 Hooks 具有如下优势:

  • 更简洁的书写;
  • 相对类中的 HOCrender Props , Hooks 拥有更加自由地组合抽象的能力;

使用 Hooks 的注意项

  • hooks 中每一次 render 都有自己的 stateprops , 这与 class 中存在差异, 见 Hooks 每次渲染都是闭包

    • class 中可以用闭包模拟 hooks 的表现, 链接 , hooks 中可以使用 ref 模拟 class 的表现, 链接 ;
  • 写出 useEffect 的所用到的依赖

在以下 demo 中, useEffect 的第二个参数传入 [] , 希望的是 useEffect 里的函数只执行一次(类似在 componentDidMount 中执行一次, 但是注意这里仅仅是 类似 , 详细原因见上一条注意项), 页面上每隔 1s 递增 1。

function Demo() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => {
      clearInterval(id);
    };
  }, []);

  return count;
}

但这样达到我们预期的效果了么? demo , 可以看到界面上只增加到 1 就停止了。原因就是传入的第二个参数 [] 搞的鬼, [] 表示没有外界状态对 effect 产生干扰。流程大致如下:

  1. 第一次调用 useEffect 传入的 count 为 0, 于是 setCount(0 + 1) ;
  2. useEffect 第二个参数 [] 的影响, count 仍然为 0, 所以相当于还是 setCount(0 + 1) ;

那如何修正上述问题呢? 方法有两个(方法一为主, 方法二为辅):

  • 方法一: 将 [] 改为 [count]
  • 方法二: 将 setCount(count + 1) 改为 setCount(count => count + 1) 。这种方法的思想是修正状态的值而不依赖外面传进的状态。

不过遇到 setCount(count => count + 1) 的情况就可以考虑使用 useReducer 了。

何时使用 useReducer

使用 useState 的地方都能用 useReducer 进行替代。相较 useState , useReducer 有如下优势:

  • useReducerhow (reducer) 和 what (dispatch(action)) 进行抽离; 使用 reducer 逻辑状态进行集中化维护;
  • 相比 useState, useReducer 没有 闭包问题 ;
  • 当状态的一个 state 依赖状态中的另一个 state 时, 这种情况最好使用 useReducer; 可以参考 decoupling-updates-from-actions 中 Dan 列举的 demo。

处理 useEffect 中的公用函数

function Demo() {
  const [count, setCount] = useState(0);

  function getFetchUrl(query) {
    return `http://demo${query}`
  }

  useEffect(() => {
    const url = getFetchUrl('react')
  }, [getFetchUrl]);

  useEffect(() => {
    const url = getFetchUrl('redux')
  }, [getFetchUrl]);

  return count;
}

此时 useEffect 中传入的第二个参数 getFetchUrl 相当于每次都是新的, 所以每次都会请求数据, 那除了 [getFetchUrl] 将改为 [] 这种不推荐的写法外,有两种解决方法:

*. 方法一: 提升 getFetchUrl 的作用域;

*. 方法二: 使用 useCallback 或者 useMemo 来包裹 getFetchUrl;

React.memo 修饰一个函数组件, useMemo 修饰一个函数。它们本质都是运用缓存。

React Hooks 内部是怎么工作的

为了理解 React Hooks 内部实现原理, 对 useStateuseEffect 进行了简单的实现。

useState 的简单实现

使用闭包来实现 useState 的简单逻辑:

// 这里使用闭包
const React = (function() {
  let _val

  return {
    useState(initialValue) {
      _val = _val || initialValue

      function setVal(value) {
        _val = value
      }

      return [_val, setVal]
    }
  }
})()

测试如下:

function Counter() {
  const [count, setCount] = React.useState(0)

  return {
    render: () => console.log(count),
    click: () => setCount(count + 1)
  }
}

Counter().render() // 0
Counter().click()  // 模拟点击
Counter().render() // 1

useEffect 的简单实现

var React = (function() {
  let _val, _deps

  return {
    useState(initialValue) {
      _val = _val || initialValue

      function setVal(value) {
        _val = value
      }

      return [_val, setVal]
    },
    useEffect(callback, deps) {
      const ifUpdate = !deps

      // 判断 Deps 中的依赖是否改变
      const ifDepsChange = _deps ? !_deps.every((r, index) => r === deps[index]) : true

      if (ifUpdate || ifDepsChange) {
        callback()

        _deps = deps || []
      }
    }
  }
})()

测试代码如下:

var {useState, useEffect} = React

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

  useEffect(() => {
    console.log('useEffect', count)
  }, [count])

  return {
    render: () => console.log('render', count),
    click: () => setCount(count + 1),
    noop: () => setCount(count), // 保持不变, 观察 useEffect 是否被调用
  }
}

Counter().render() // 'useEffect' 0, 'render', 0
Counter().noop()
Counter().render() // 'render', 0
Counter().click()
Counter().render() // 'useEffect' 1, 'render', 1

处理多次调用的情形

为了在 hooks 中能使用多次 useState , useEffect , 将各个 useState , useEffect 的调用存进一个数组中, 在上面基础上进行如下改造:

const React = (function() {
  const hooks = []
  let currentHook = 0

  return {
    render(Component) {
      const component = Component()
      component.render()
      currentHook = 0 // 重置, 这里很关键, 将 hooks 的执行放到 hooks 队列中, 确保每次执行的顺序保持一致。
      return component
    },
    useState(initialValue) {
      hooks[currentHook] = hooks[currentHook] || initialValue

      function setVal(value) {
        hooks[currentHook] = value
      }

      return [hooks[currentHook++], setVal]
    },
    useEffect(callback, deps) {
      const ifUpdate = !deps

      // 判断 Deps 中的依赖是否改变
      const ifDepsChange = hooks[currentHook] ? !hooks[currentHook].every((r, index) => r === deps[index]) : true

      if (ifUpdate || ifDepsChange) {
        callback()

        hooks[currentHook++] = deps || []
      }
    }
  }
})()

测试代码如下:

var {useState, useEffect} = React

function Counter() {
  const [count, setCount] = useState(0)
  const [type, setType] = useState('hi')

  useEffect(() => {
    console.log('useEffect', count)
    console.log('type', type)
  }, [count, type])

  return {
    render: () => console.log('render', count),
    click: () => setCount(count + 1),
    noop: () => setCount(count), // 保持不变, 观察 useEffect 是否被调用
  }
}

/* 如下 mock 执行了 useEffect、render; 这里使用 React.render 的原因是为了重置 currentHook 的值 */
let comp = React.render(Counter) // useEffect 0 type hi render 0

/* 如下 mock 只执行了 render */
comp.noop()
comp = React.render(Counter) // render 0

/* 如下 mock 重新执行了 useEffect、render */
comp.click()
React.render(Counter) // useEffect 1, render 1

以上所述就是小编给大家介绍的《React Hooks 深入系列》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

高效算法

高效算法

[法] Christoph Dürr、[法] Jill-Jênn Vie / 史世强 / 人民邮电出版社 / 2018-5 / 55.00元

本书旨在探讨如何优化算法效率,详细阐述了经典算法和特殊算法的实现、应用技巧和复杂度验证过程,内容由浅入深,能帮助读者快速掌握复杂度适当、正确率高的高效编程方法以及自检、自测技巧,是参加ACM/ICPC、Google Code Jam 等国际编程竞赛、备战编程考试、提高编程效率、优化编程方法的参考书目。一起来看看 《高效算法》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具