为什么要用Hooks?

在 React 引入 Hooks 之前,我们要实现组件功能复用,常用技巧无非 HOC 和 Render Props 。下面就这两个技巧简单说一下,毕竟我们的重点是 Hooks

高阶组件(HOC)

高阶组件:参数为组件,返回值为新组件的函数(很多文章说返回的新组件是高阶组件,其实是不准确的)

高阶组件主要用于共享组件代码,强化组件功能等场景,也就是达到DRY模式。如

//原组件(参数组件)
class Test extends Component{
    render() {
        return (
            <div>
                <p>{this.props.title} - {this.props.name}</p>
                {this.props.children}
            </div>
        );
    }
}

//返回函数组件的高阶组件
const withTest = Comp => {
    const name = '高阶组件';
    return props => <Comp {...props} name={name} />
}

//返回class组件的高阶组件
const withTest = Comp => {
    return class extends Component{
        render() {
            return (
                <Comp {...this.props}>
                    <span>this is Hoc</span>
                </Comp>
            );
        }
    }
}
复制代码

特点:

  1. 高阶组件是纯函数,不产生任何副作用;
  2. 返回的新组件和参数传入的组件是两个完全独立的组件;
  3. 不要在render方法中使用HOC,会发生组件不必要的卸载和重新渲染,影响性能问题;
  4. 组件的ref不会被高阶组件获取,需要采用Refs转发;

给高阶组件传参

//hoc.tsx
export default (wrapProps) => (WrappedComponent) => (props) => (
  <WrappedComponent {...props} {...wrapProps} />
);

//index.tsx
import HOC from './hoc'
const Demo = HOC(props)(demo)
复制代码

一般用于高阶组件条件渲染或传参,如 react-redux 的 connect

高阶组件反向继承

高阶组件继承入参组件,对入参组件进行操作

export default (WrappedComponent) => {
  return class Inheritance extends WrappedComponent {
    componentDidMount() {
      // 可以方便地得到state,做一些更深入的修改。
      console.log(this.state);
    }
    render() {
      return super.render();
    }
  }
}
复制代码

ES7装饰器使用高阶组件

@withHeader
@withLoading
class Demo extends Component{

}
//执行顺序 withLoading => withHeader,即从内到外执行
const enhance = recompact.compose(withHeader,withLoading);
@enhance
class Demo extends Component{

}
//也可以采用recompact组合高阶组件,从后往前执行
复制代码

优缺点

  • 优点:组件逻辑复用性强,并且可以传参增强组件功能
  • 缺点:加深组件嵌套层级,且多层高阶组件嵌套时容易出现props同名,无法区分的问题

PS:可以通过react-devtools等chrome插件查看组件层级

Render Props 模式

传递一个返回渲染组件的函数,由父组件决定子组件children的渲染内容

class Mouse extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div style={{ height: '100%' }}>
        {this.props.render(this.state)}
        //<Cat /> 不采用这种硬编程的方法写死组件
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移动鼠标!</h1>
        <Mouse render={name => (
          <Cat name={name} />
        )}/>
      </div>
    );
  }
}
复制代码

特点:

  1. Render Props 模式中的“render”可以任何名字,但如果命名成children,需要在propType中指定children为function;
  2. 与PureComponent一起使用时,切记不要传匿名函数。因为PureComponent是基于props的浅比较,每次发生更新比较时,都是一个新的匿名函数,浅比较结果都返回false,发生子组件重新渲染,从而失去了PureComponent组件原有的性能优化功能

优点

Render Props 模式很好地规避了HOC的缺点,不会产生无用的组件加深层级。但是使用时,应充分了解React组件渲染原理,使用不当可能会造成应用性能的不必要开销

Hooks出现

好了,终于轮到今天的主角。在聊 Hooks 之前,我们说一下 Class 组件的弊端:

  1. 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。往往需要借助上面提到的hoc、render prop等技巧实现功能复用,从而加深了组件嵌套层级;
  2. Class编写的组件代码较“重”,不管是一个多小的组件都必须是一个class实例,且存在难以理解的this指向问题(虽然可以通过一些babel polyfill解决)。

React@16.8推出了Hooks概念以来,我们完全可以编写更加灵活的函数组件,并且可以实现自定义hooks来满足不同的业务需求

常用Hooks

useState

用于函数组件添加state变量。普通函数退出后变量就会”消失”,而 state 中的变量会被 React 保留

import React, { useState } from 'react';
function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);
//左边采用数组解析构,第一个变量是自定义state,第二个是对应的操作函数
//useState参数是state变量的初始值,可以是基本数据类型,数组,对象等
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

## 相当于class组件的this.state
this.state = { count: 0 };
复制代码

用定义state变量时返回的操作函数修改state的值

setCount(1);

##相当于class组件的setState
this.setState({ count: this.state.count + 1 })};
复制代码

⚠️注意:与setState不同的是,更新state变量总是替换它而不是合并它,所以在update复杂结构state值的时候需要采用函数式更新

const [person, setPerson] = useState({name, age})
setPerson(pre => ({...person, age}))
复制代码

useEffect

为函数组件提供执行副作用操作的能力,例如数据获取,设置订阅以及手动更改 React 组件中的 DOM 等。useEffect Hook 相当于componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合

function Example() {
  const [times, setTimes] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${times} times`;
  }, [times]);
}
复制代码
  • 执行机制:在函数组件内部调用useEffect Hook,第一个参数是回调函数(称之为effect)。在函数组件发生渲染时,都会执行该回调函数(包括首次渲染和更新渲染)
  • effect清除:effect中可能会有一些占用内存的操作,例如数据订阅等,需要在组件卸载的时候进行清除,释放内存。effect约定在执行下一次effect执行前或者组件卸载前执行effect中返回的函数进行清除操作,如下
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [times, setTimes] = useState(0);
  useEffect(() => {
    console.log('effect running', times);
    return () => {
      console.log('effect clear');
    }
  }, [times]);
  const onClickHandle = () => {
      setTimes(++times)
  }
  return (<Button type="default" onClick={onClickHandle}></Button>);
}
//每次点击button(组建重新渲染)都会依次打印:
//effect running
//effect clear
复制代码
  • 性能优化:有时候并不希望每次发生组件重新渲染时,都执行effect,可以给useEffect传入第二个参数。第二个参数是一个依赖项数组,表示effect执行依赖哪些数据修改,默认值是 [] 。如果依赖的数据重新渲染前后没有发生改变,effect将不会被执行

useRef

生成一个ref对象,ref.current 属性被初始化为传入的参数,该值在组件的整个生命周期内保持不变,每次渲染时返回同一个 ref 对象

const inputEl = useRef(Date.now());
复制代码

与createRef()不同的是:

  • createRef生成的ref.current未被使用在DOM元素或者组件上时,始终是null,使用之后指向实际DOM或者组件实例;
  • useRef生成的ref.current被初始化为传入的参数,且在组件整个生命周期内不变,绑定到DOM后,.current指向实际DOM

useImperativeHandle对应与ref转发,与 forwardRef 一起使用

useImperativeHandle(ref, createHandle, [deps])
复制代码

useMemo

利用“创建函数”和依赖项数组,生成一个memoized值,且仅当某个依赖项渲染前后的值发生改变时,“创建函数”才会执行重新计算memoized值。如:

const memoizedValue = useMemo(() => return Math.sum(a, b), [a, b]);
复制代码
  • 创建函数在首次渲染时无条件执行;
  • 依赖项数组默认是 [],没有依赖项时,每次发生组件重新渲染都会执行;
  • 不要在“创建函数”中执行与渲染无关的操作,记住与useEffect的区别

useCallback

把内联回调函数及依赖项数组作为参数传入 useCallback,返回该回调函数的 memoized 版本,回调函数仅在某个依赖项改变时才会更新。相当于useMemo(() => fn, deps)返回传入的回调函数,而不是计算值

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
复制代码

可以在某些特定的场景下调用memoizedCallback,而不需要触发组件重新渲染

更多参考 React文档

自定义Hooks

React提供的Hooks毕竟有限,实际开发中我们往往需要自定义一些hook。这里写一个useFetch自定义hook的例子

 export function useFetch(url, options) {
  const [data, setData] = useState({});

  const fetchApi = useCallback(async () => {
    const res = await fetch(url, options);
    if (res.ok && res.status === 200) {
      setData(res.json());
    }
  }, [url]);

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

  return data;
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享