美文网首页
谈谈react hooks的优缺点

谈谈react hooks的优缺点

作者: Yong_bcf4 | 来源:发表于2021-03-13 18:20 被阅读0次

    下面我谈一下我认为的react hooks的优缺点,优缺点通过和传统的React.Component进行对比得出。


    优点

    一、更容易复用代码

    这点应该是react hooks最大的优点,它通过自定义hooks来复用状态,从而解决了类组件有些时候难以复用逻辑的问题。hooks是怎么解决这个复用的问题呢,具体如下:

    1. 每调用useHook一次都会生成一份独立的状态,这个没有什么黑魔法,函数每次调用都会开辟一份独立的内存空间。
    2. 虽然状态(from useState)和副作用(useEffect)的存在依赖于组件,但它们可以在组件外部进行定义。这点是class component做不到的,你无法在外部声明state和副作用(如componentDidMount)。

    上面这两点,高阶组件和renderProps也同样能做到。但hooks实现起来的代码量更少,以及更直观(代码可读性)。

    举个例子,我们经常使用的antd-table,我们经常需要写一些状态:pagination={current:xxx,pageSize:xxx,total:xxx}。但许多场景只是简单的表格,我们希望封装一个高阶组件,自带这些状态,并可以自动调用server去获取remote data。

    用高阶组件来实现的话会是这样:

    import { Table } from 'antd'
    import server from './api'
    
    function useTable(server) {
      return function (WrappedComponent) {
        return class HighComponent extends React.Component {
          state = {
            tableProps: xxx
          };
          render() {
            const { tableProps } = this.state;
            return <WrappedComponent tableProps={tableProps} />;
          }
        };
      };
    }
    
    @useTable(server)
    class App extends Component{
      render(){
    /**
     * 高阶组件/renderProps是通过增强组件的props(赋予一个新的属性或者方法到组件的props属性),
     * 实现起来比较隐式。你难以区分这个props是来自哪个高阶组件(特别是使用了较多的高阶组件时),
     * 或者还是来自业务的父组件。
     */
        const { tableProps } = this.props;
        return (
          <Table 
            columns={[...]}
          // tableProps包含pagination, onChange, dataSource等属性。
            {...tableProps}
          />
        )
      }
    }
    
    

    用hooks来实现的话,会是:

    import { Table } from 'antd'
    import server from './api'
    
    function useTable(server) {
      const [tableProps, setTableProps] = useState(xxx);
      return tableProps;
    }
    
    function App {
        const { tableProps } = useTable();
        return (
          <Table 
            columns={[...]}
          // tableProps包含pagination, onChange, dataSource等属性
            {...tableProps}
          />
        )
    }
    /*
    相对比高阶组件“祖父=>父=>子”的层层嵌套,
    hooks是这样的:  
    const { brother1 } = usehook1; 
    const { brother2} = usehook2;
    */
    
    

    可以看到,hooks的逻辑更清晰,可读性更好。

    二、清爽的代码风格+代码量更少

    1. 函数式编程风格,函数式组件、状态保存在运行环境、每个功能都包裹在函数中,整体风格更清爽,更优雅。

    2. 对IDE更友好,对比类组件,函数组件里面的unused状态和unused-method更容易被编辑器发现。

    3. 使用typescript的话,类型声明也变得更容易。

    class Example{
       hello: string;
       constructor(){
          this.hello = 'hello world'
       }
    }
    
    // 代码量更少
    function Example(){
       const hello:string = 'hello world'
    }
    
    

    4. 向props或状态取值更加方便,函数组件的取值都从当前作用域直接获取变量,而类组件需要先访问实例this,再访问其属性或者方法,多了一步。

    5. 更改状态也变得更加简单, this.setState({ count:xxx })变成 setCount(xxx)

    因为减少了很多模板代码,特别是小组件写起来更加省事,人们更愿意去拆分组件。而组件粒度越细,被复用的可能性越大。所以,hooks也在不知不觉中改变人们的开发习惯,提高项目的组件复用率。


    缺点

    一、响应式的useEffect

    写函数组件时,你不得不改变一些写法习惯。你必须清楚代码中useEffectuseCallback的“依赖项数组”的改变时机。有时候,你的useEffect依赖某个函数的不可变性,这个函数的不可变性又依赖于另一个函数的不可变性,这样便形成了一条依赖链。一旦这条依赖链的某个节点意外地被改变了,你的useEffect就被意外地触发了,如果你的useEffect是幂等的操作,可能带来的是性能层次的问题,如果是非幂等,那就糟糕了。

    所以,对比componentDidmountcomponentDidUpdate,useEffect带来的心智负担更大。

    二、状态不同步

    函数的运行是独立的,每个函数都有一份独立的作用域。函数的变量是保存在运行时的作用域里面,当我们有异步操作的时候,经常会碰到异步回调的变量引用是之前的,也就是旧的(这里也可以理解成闭包)。比如下面的一个例子(codesandbox):

    import React, { useState } from "react";
    ​
    const Counter = () => {
      const [counter, setCounter] = useState(0);
    ​
      const onAlertButtonClick = () => {
        setTimeout(() => {
          alert("Value: " + counter);
        }, 3000);
      };
    ​
      return (
        <div>
          <p>You clicked {counter} times.</p>
          <button onClick={() => setCounter(counter + 1)}>Click me</button>
          <button onClick={onAlertButtonClick}>
            Show me the value in 3 seconds
          </button>
        </div>
      );
    };
    ​
    export default Counter;
    
    

    当你点击Show me the value in 3 seconds的后,紧接着点击Click me使得counter的值从0变成1。三秒后,定时器触发,但alert出来的是0(旧值),但我们希望的结果是当前的状态1。

    这个问题在class component不会出现,因为class component的属性和方法都存放在一个instance上,调用方式是:this.state.xxxthis.method()。因为每次都是从一个不变的instance上进行取值,所以不存在引用是旧的问题。

    其实解决这个hooks的问题也可以参照类的instance。用useRef返回的immutable RefObject(current属性是可变的)来保存state,然后取值方式从counter变成了: counterRef.current。如下:

    import React, { useState, useRef, useEffect } from "react";
    ​
    const Counter = () => {
      const [counter, setCounter] = useState(0);
      const counterRef = useRef(counter);
    ​
      const onAlertButtonClick = () => {
        setTimeout(() => {
          alert("Value: " + counterRef.current);
        }, 3000);
      };
    ​
      useEffect(() => {
        counterRef.current = counter;
      });
    ​
      return (
        <div>
          <p>You clicked {counter} times.</p>
          <button onClick={() => setCounter(counter + 1)}>Click me</button>
          <button onClick={onAlertButtonClick}>
            Show me the value in 3 seconds
          </button>
        </div>
      );
    };
    ​
    export default Counter;
    
    

    结果如我们所期待,alert的是当前的值1。

    我们可以把这个过程封装成一个custom hook,如下:

    import { useEffect, useRef, useState } from "react";
    ​
    const useRefState = <T>(
      initialValue: T
    ): [T, React.MutableRefObject<T>, React.Dispatch<React.SetStateAction<T>>] => {
      const [state, setState] = useState<T>(initialValue);
      const stateRef = useRef(state);
      useEffect(() => {
        stateRef.current = state;
      }, [state]);
      return [state, stateRef, setState];
    };
    ​
    export default useRefState;
    ​
    
    

    尽管这个问题被巧妙地解决了,但它不优雅、hack味道浓,且丢失了函数编程风格。


    怎么避免react hooks的常见问题

    1. 不要在useEffect里面写太多的依赖项,划分这些依赖项成多个单一功能的useEffect。其实这点是遵循了软件设计的“单一职责模式”。
    2. 如果你碰到状态不同步的问题,可以考虑下手动传递参数到函数。如:
       // showCount的count来自父级作用域 
       const [count,setCount] = useState(xxx); 
       function showCount(){ console.log(count) } 
    
       // showCount的count来自参数 
       const [count,setCount] = useState(xxx); 
       function showCount(c){ console.log(c) }
    
    

    但这个也只能解决一部分问题,很多时候你不得不使用上述的useRef方案。

    3. 重视eslint-plugin-react-hooks插件的警告。

    4. 复杂业务的时候,使用Component代替hooks。


    感想

    目前,我通常更偏向于用hooks来写组件,但在复杂业务中,我会更倾向于用class Component或者两者结合的方式。hooks会是未来的主流组件编写方式,但目前来说似乎它还不太成熟。

    相关文章

      网友评论

          本文标题:谈谈react hooks的优缺点

          本文链接:https://www.haomeiwen.com/subject/uchszktx.html