React Hooks原理与实践

什么是React Hooks?

首先须要了解react组件的概念

react组件分为如下几种:vue

一、功能组件(无状态组件)react

Functional (Stateless) Component,功能组件也叫无状态组件,通常只负责渲染。redux

function Welcome(props) {
    return <h1>Hello, {props.name}</h1>; 
}
复制代码

二、类组件(有状态组件)
Class (Stateful) Component,类组件也是有状态组件,也能够叫容器组件。通常有交互逻辑和业务逻辑。数组

class Welcome extends React.Component {
  state = {
      name: ‘tori’,
  }
componentDidMount() {
      fetch(…);
      …
  }
render() {
  return (
      <> <h1>Hello, {this.state.name}</h1> <button onClick={() => this.setState({name: ‘007’})}>更名</button> </>
    );
}
}
复制代码

三、渲染组件 Presentational Component,和功能(无状态)组件相似。浏览器

const Hello = (props) => {
  return (
    <div> <h1>Hello! {props.name}</h1> </div>
  )
}
复制代码

总结

  • 函数组件必定是无状态组件,展现型组件通常是无状态组件;
  • 类组件既能够是有状态组件,又能够是无状态组件;
  • 容器型组件通常是有状态组件。
  • 划分的原则归纳为:分而治之、高内聚、低耦合
  • 经过以上组件之间的组合能实现绝大部分需求。

Hook 出现以前,组件之间复用状态逻辑很难,解决方案(HOC、Render Props)都须要从新组织组件结构, 且代码难以理解。在React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其余抽象层组成的组件会造成“嵌套地狱”。
组件维护愈来愈复杂,譬如事件监听逻辑要在不一样的生命周期中绑定和解绑,复杂的页面componentDidMount包涵不少逻辑,代码阅读性变得不好。
class组件中的this难以理解,且class 不能很好的压缩,而且会使热重载出现不稳定的状况。更多引子介绍参见官方介绍。
因此hook就为解决这些问题而来:缓存

  • 避免地狱式嵌套,可读性提升。
  • 函数式组件,比class更容易理解。
  • class组件生命周期太多太复杂,使函数组件存在状态。
  • 解决HOC和Render Props的缺点。
  • UI 和 逻辑更容易分离。

官方hooks API

1.useState

useState 是 React Hooks 中很基本的一个 API,它的用法主要有这几种:性能优化

  1. useState 接收一个初始值,返回一个数组,数组里面分别是当前值和修改这个值的方法(相似 state 和 setState)。
  2. useState 接收一个函数,返回一个数组。
  3. setCount 能够接收新值,也能够接收一个返回新值的函数。
1.  const [ count1, setCount1 ] = useState(0);
1.  const [ count2, setCount2 ] = useState(() => 0);
1.  setCount1(1); // 修改 state
复制代码

class this.setState更新是state是合并, useState中setState是替换。markdown

useState 和 class state 的区别 虽然函数组件也有了 state,可是 function state 和 class state 仍是有一些差别:闭包

  1. function state 的粒度更细,class state 过于无脑。
  2. function state 保存的是快照,class state 保存的是最新值。
  3. 引用类型的状况下,class state 不须要传入新的引用,而 function state 必须保证是个新的引用。

关于第2点,举个例子less

image.png

image.png

在第一个例子中,连续点击十次,页面上的数字会从0增加到10。而第二个例子中,连续点击十次,页面上的数字只会从0增加到1

class 组件里面能够经过 this.state 引用到 count,因此每次 setTimeout 的时候都能经过引用拿到上一次的最新 count,因此点击多少次最后就加了多少。

在 function component 里面每次更新都是从新执行当前函数,也就是说 setTimeout 里面读取到的 count 是经过闭包获取的,而这个 count 实际上只是初始值,并非上次执行完成后的最新值,因此最后只加了1次。

2.useRef

要解决上面这个问题,就须要使用useRef,useRef是一个对象,他拥有一个current属性,而且无论函数组件执行多少次,useRef返回的对象永远都是原来的那一个

image.png

useRef 有下面这几个特色:

  1. useRef 是一个只能用于函数组件的方法。
  2. useRef 是除字符串 ref、函数 refcreateRef 以外的第四种获取 ref 的方法。
  3. useRef 在渲染周期内永远不会变,所以能够用来引用某些数据。
  4. 修改 ref.current 不会引起组件从新渲染。

3.useEffect

useEffect 是一个 Effect Hook,经常使用于一些反作用的操做,在必定程度上能够充当 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个生命周期。useEffect 是很是重要的一个方法,能够说是 React Hooks 的灵魂,它用法主要有这么几种:

  1. useEffect 接收两个参数,分别是要执行的回调函数、依赖数组。
  2. 若是依赖数组为空数组,那么回调函数会在第一次渲染结束后(componentDidMount)执行,返回的函数会在组件卸载时(componentWillUnmount)执行。
  3. 若是不传依赖数组,那么回调函数会在每一次渲染结束后(componentDidMount 和 componentDidUpdate)执行。
  4. 若是依赖数组不为空数组,那么回调函数会在依赖值每次更新渲染结束后(componentDidUpdate)执行,这个依赖值通常是 state 或者 props。

image.png

useEffect 比较重要,它主要有这几个做用:

  1. 代替部分生命周期,如 componentDidMount、componentDidUpdate、componentWillUnmount。
  2. 更加 reactive,相似 mobx 的 reaction 和 vue 的 watch。
  3. 从命令式变成声明式,不须要再关注应该在哪一步作某些操做,只须要关注依赖数据。
  4. 经过 useEffect 和 useState 能够编写一系列自定义的 Hook。
useEffect vs useLayoutEffect

useLayoutEffect 也是一个 Hook 方法,从名字上看和 useEffect 差很少,他俩用法也比较像。在90%的场景下咱们都会用 useEffect,然而在某些场景下却不得不用 useLayoutEffect。useEffect 和 useLayoutEffect 的区别是:

  1. useEffect 不会阻塞浏览器渲染,而 useLayoutEffect 会。
  2. useEffect 会在浏览器渲染结束后执行,useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制以前执行。

例如咱们使用useEffect方法来更新Demo的位置,那么在页面渲染时,咱们就会看到Demo原来的位置,而后这个时候useEffect方法才会执行,更新Demo的位置,咱们就会在页面上看到Demo位置的变化,可是有的时候咱们不想让用户看到这个变化的过程,会比较丑,好比变动一个元素的位置,就会变成闪现过去,这个时候就须要使用useLayoutEffect

4.useContext

跨组件共享数据的钩子函数

const value = useContext(MyContext); 
// MyContext 为 context 对象(React.createContext 的返回值) 
// useContext 返回MyContext的返回值。 
// 当前的 context 值由上层组件中距离当前组件最近的<MyContext.Provider> 的 value prop 决定。
复制代码

useContext 的组件总会在 context 值变化时从新渲染, 因此<MyContext.Provider>包裹的越多,层级越深,性能会形成影响。 <MyContext.Provider>的value 发生变化时候,包裹的组件不管是否订阅content value,全部组件都会从新渲染。

5.useReducer

const [state, dispatch] = useReducer(reducer, initialState);
复制代码

reducer就是一个只能经过action将state从一个过程转换成另外一个过程的纯函数;

useReducer就是一种经过(state,action) => newState的过程,和redux工做方式同样。 数据流: dispatch(action) => reducer更新state => 返回更新后的state

官方推荐如下场景须要useReducer更佳:

  • state 逻辑较复杂且包含多个子值, 能够集中处理。
  • 下一个 state 依赖于以前的 state。
  • 想更稳定的构建自动化测试用例。
  • 想深层级修改子组件的一些状态,使用 useReducer 还能给那些会触发深更新的组件作性能优化,由于你能够向子组件传递 dispatch 而不是回调函数 。

使用reducer有助于将读取与写入分开。

6.useMemo

useMemo 的用法相似 useEffect,经常用于缓存一些复杂计算的结果。useMemo 接收一个函数和依赖数组,当数组中依赖项变化的时候,这个函数就会执行,返回新的值。

const sum = useMemo(() => {
    // 一系列计算
}, [count])
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);; 返回一个 memoized 值,和useCallback同样,当依赖项发生变化,才会从新计算 memoized 的值。useMemo和useCallback不一样之处是:它容许你将memoized应用于任何值类型(不只仅是函数)。
复制代码

image.png DatePicker 组件每次打开或者切换月份的时候,都须要大量的计算来算出当前须要展现哪些日期。而后再将计算后的结果渲染到单元格里面,这里可使用 useMemo 来缓存,只有当传入的日期变化时才去计算。

  • useMemo会在render前执行
  • 若是没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
  • useMemo用于返回memoize,防止每次render时大计算量带来的开销。
  • 使用useMemo优化需谨慎,由于优化自己也带来了计算,大多数时候,你不须要考虑去优化没必要要的从新渲染

7.useCallback

和 useMemo 相似,只不过 useCallback 是用来缓存函数。

const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
//返回一个 memoized 回调函数。
复制代码

总结: useCallback将返回一个记忆的回调版本,仅在其中一个依赖项已更改时才更改。当将回调传递给依赖于引用相等性的优化子组件以防止没必要要的渲染时,此方法颇有用。使用回调函数做为参数传递,每次render函数都会变化,也会致使子组件rerender, useCallback能够优化rerender。

相关文章
相关标签/搜索