React-Hook最佳实践

React Hook 新出现背景

类组件的问题

  • 复用组件状态难,高阶组件+渲染属性 providers customers,等一堆工具都是为了解决这个问题,但是造成了很严重的理解成本和组件嵌套地狱
  • 生命周期带来的负面影响,逻辑拆分严重
  • This 的指向问题

函数组件的局限

  • 之前函数组件没有 state 和 生命周期,导致使用场景有限

React Hook

HooksReact 16.8 新增的特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性,无需转化成类组件

Hook 的使用和实践

useStateHook 的闭包机制

// hook 组件
function Counter() {
   
  const [count, setCount] = useState(0);
  const log = () => {
   
    setCount(count + 1);
    setTimeout(() => {
   
      console.log(count);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {
   count} times</p>
      <button onClick={
   log}>Click me</button>       </div>
  );
}

// 等效的类组件
class Counter extends Component {
   
  state = {
    count: 0 };
  log = () => {
   
    this.setState({
   
      count: this.state.count + 1,
    });
    setTimeout(() => {
   
      console.log(this.state.count);
    }, 3000);
  };
  render() {
   
    return (
      <div>
        <p>You clicked {
   this.state.count} times</p>
        <button onClick={
   this.log}>Click me</button>            </div>
    );
  }
}

快速点击下的情况下,想想 Hook 组件和函数式组件控制台打印出来的是什么?

  • 类组件打印出来的是 3 3 3
    Class 组件的 state 是不可变的,通过 setState 返回一个新的引用,this.state 指向一个新的引用
    setTimeout 执行的时候,通过 this 获取最新的 state 引用,所以这个输出都是 3
  • 函数组件打印的结果是 0 1 2
    函数组件闭包机制,函数组件每一次渲染都有独立的 propsstate
    每一次渲染都有独立的事件处理函数
    每一次渲染的状态不会受到后面事件处理的影响

函数组件渲染拆解

既然每次渲染都是一个独立的闭包,可以尝试代码拆解函数式组件的渲染过程

// 第一次点击
function Counter() {
   
  const [0, setCount] = useState(0);  
  const log = () => {
   
    setCount(0 + 1);
    // 只能获取这次点击按钮的 state
    setTimeout(() => {
   
      console.log(0);
    }, 3000);
  };
}
// 第二次点击
function Counter() {
   
  const [1, setCount] = useState(0);  
  const log = () => {
   
   setCount(1 + 1);
    setTimeout(() => {
   
      console.log(1);
    }, 3000);
  };
}
// 第三次点击
function Counter() {
   
  const [2, setCount] = useState(0); 
  const log = () => {
   
    setCount(2 + 1);
    setTimeout(() => {
   
      console.log(2);
    }, 3000);
  };
}

  • 三次点击,共 4 次渲染,count0 变为 3

  • 页面第一次渲染,页面看到的 count = 0

  • 第一次点击,事件处理器获取的 count = 0count 变成 1, 第二次渲染,渲染后页面看到 count = 1,对应上述代码第一次点击

  • 第二次点击,事件处理器获取的 count = 1count 变成 2, 第三次渲染,渲染后页面看到 count = 2,对应上述代码第二次点击

  • 第三次点击,事件处理器获取的 count = 2count 变成 3, 第四次渲染,渲染后页面看到 count = 3,对应上述代码第三次点击

让函数式组件也可以输出 3 3 3

有种比较简单并且能解决问题的方案,借用 useRef

  • useRef 返回一个可变的 ref 对象,其  current  属性被初始化为传入的参数(initialValue)
  • useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的 ref 对象都是同一个
  • useRef 可以类比成类组件实例化后的 this,在组件没有销毁的返回的引用都是同一个
function Counter() {
   
  const count = useRef(0);
  const log = () => {
   
    count.current++;
    setTimeout(() => {
   
      console.log(count.current);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {
   count.current} times</p>
      <button onClick={
   log}>Click me</button>        </div>
  );
}

  • 这样修改一下,控制台输出的确实是 3 3 3
  • ? 既然 Ref 对象整个生命周期都不变,修改 current 属性也只是修改属性,那除了打印,这里的 You clicked 0 times ,点击三次,会变成 3 么?
  • 显然不能,这个组件没有任何的属性和状态改变,会重新渲染才怪,所以这里虽然点击了 3 次,但是不会像 useState 一样,渲染 4 次,这里只会渲染 1 次,然后看到的都是 You clicked 0 times
  • 修复一个问题把另外一个更大的问题引进来,这很程序员。。。

useEffect

通过 useRef 虽然能解决打印的问题,但是页面渲染是不对的,这里还是使用 useState 的方案,配合 useEffect 可以实现我们想要的效果

function useEffect(effect: EffectCallback, deps?: DependencyList): void;

  • 看下 useEffect 的签名,effect 是函数类型,并且必填, 还有第二个可选参数,类型是只读数组
  • useEffect  是处理副作用的,其执行时机在   每次 Render 渲染完毕后,换句话说就是每次渲染都会执行,在真实 DOM 操作完毕后。

配合这个 hook, 如果每次 state 改变后渲染完之后,把 ref 里面的值更新,然后控制台打印 ref 的值,参考React实战视频讲解:进入学习

function Counter() {
   
  const [count, setCount] = useState(0);
  const currentCount = useRef(count);
  useEffect(() => {
   
    currentCount.current = count;
  });
  const log = () => {
   
    setCount(count + 1);
    setTimeout(() => {
   
      console.log(currentCount.current);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {
   count} times
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值