React Hooks 是 React v16.8 中新增加的个性,这个个性次要是对函数型组件的性能进行加强,让函数型组件能够做类组件做的事件。

这个篇文章次要蕴含三个局部:

  1. 什么是react hooks

    1. react hooks 次要蕴含哪些性能,要解决哪些问题。
  2. react hooks 的应用

    1. react hooks 到底是什么
    2. 这些函数要在什么场景下应用
  3. 自定义 react hooks

    1. 通过本人定义函数,如何将组件外部的公共逻辑提取进去

1. 什么是 React Hooks

1.react hooks 性能介绍

对函数组件进行加强,让函数组件能够贮存状态,能够领有解决副作用的能力,让开发者在不应用类组件的状况下,实现雷同的性能。

副作用:在组件中不是将数据转换为视图的代码被称为副作用。

比方:获取DOM元素、为DOM元素增加事件、设置定时器以及发送Ajax申请等,这都属于副作用。

2.类组件的有余

  1. 短少逻辑复用机制

    1. 为了复用逻辑减少无理论渲染成果的组件,减少了组件层级显示非常臃肿,减少了调试的难度以及运行效率的升高。
  2. 类组件常常会变得很简单难以保护

    1. 将一组相干的业务逻辑拆分到多个生命周期函数中,例如:1.在组件挂载时要做些什么 2.更新实现之后要做些什么
    2. 在一个生命周期函数内存在多个不相干的业务逻辑
    3. 类成员的办法不能保障this指向的正确性,例如:在申明一些函数要应用bind进行绑定,或者嵌套函数来解决this指向的问题

2. React Hooks 的应用

Hooks 意为钩子,React Hooks 就是一堆钩子函数,React 通过这些钩子函数对函数组型件进行加强,不同的钩子函数提供了不同的性能。

react hooks 提供的钩子函数有:

  • useState()
  • useEffect()
  • useReducer()
  • useRef()
  • useCallback()
  • useContext()
  • useMemo()

1.useState

用于为函数组件引入状态,在现有的认知外面,在函数执行结束之后该变量就会被开释掉,所以函数型组件本来是不能够保留组件数据的,useState 用来解决这个问题

import React, {useState} from 'react';function App () {  // useState 返回一个数组,count: 状态 setCount: 设置状态的办法  const [count, setCount] = useState(0);  return <div>     <span>{count}</span>     <button onClick={() => setCount(count => count + 1)}>+1</button>  </div>}

应用 setCount 办法设置 count 的值后,组件会被从新渲染,而渲染后count的状态还在。

  • useState 应用细节

    1. 接管惟一的参数即状态初始值,初始值能够是任意数据类型。
    2. 返回为数组,数组中存储状态值和更新状态值的办法,办法名称约定以set结尾,前面加上状态的名称。
    3. useState 办法能够被调用屡次,用以保留不同的状态值。
    4. 参数能够是一个函数,函数返回什么初始状态值就是什么,函数只会被调用一次,用在初始值是动静值的状况下。
import React, {useState} from 'react';function UseState (props) {  console.log('渲染即执行');  // 传入一个办法  const [count, setCount] = useState(() => {    console.log('这个地位只会被执行一次');    return props.count || 0;  });  // 能够接管任意数据类型为状态值  const [person, setPerson] = useState({name: 'howie', age: 20});  return <div>    <span>{count} {person.name} {person.age}</span>    <button onClick={() => setCount(count + 1)}>plus</button>    <button onClick={() => setPerson(() => ({...person, name: 'zs'}))}>change person</button>  </div>}export default UseState;
  • 设置状态的细节

    1. 设置状态值的办法的参数,能够是一个值也能够是一个函数
    2. 设置状态值的办法自身是异步的
import React, {useState} from 'react';function UseState (props) {  const [count, setCount] = useState(() => {    return props.count || 0;  });  function handleSetCount () {    // 传递回调函数设置状态,setCount 是异步的    setCount(count => {      return count + 1;      // 这样设置title就会变成同步的      // const newCount = count + 1;      // document.title = newCount; // 1      // return newCount;    });    document.title = count; // 0  }  return <div>    <span>{count}</span>    <button onClick={handleSetCount}>plus</button>  </div>}export default UseState;

2.useReducer

useReducer是另一种让函数组件保留状态的形式。

import React, {useReducer} from 'react';function UseReducer () {  function reducer(state, action) {    switch (action.type) {      case 'increment':        return state + 1;      case 'decrement':        return state - 1;      default:        return state;    }  }  const [count, dispatch] = useReducer(reducer, 0);  return <div>    <p>useReducer</p>    <button onClick={() => dispatch({type: 'increment'})}>+</button>    <span>{count}</span>    <button onClick={() => dispatch({type: 'decrement'})}>-</button>  </div>}export default UseReducer;

相较于 useState 的劣势在于,如果子组件要批改父组件中的数据时,能够间接将父组件中的dispatch办法传递给子组件,
而后依据不同的类型,做不同的解决。

3.useContext

在跨组件层级获取数据时简化获取数据的代码。

  • 跨层级获取数据

    import React, {createContext} from 'react';const countContext = createContext();function UseContext () {return <div>  <header>UseContext</header>  <countContext.Provider value={100}>    <Foo/>  </countContext.Provider></div>}function Foo () {return <countContext.Consumer>  {    value => {      return <div>{value}</div>    }  }</countContext.Consumer>}export default UseContext;
  • 简化代码

    import React, {createContext, useContext} from 'react';const countContext = createContext();function UseContext () {return <div>  <header>UseContext</header>  <countContext.Provider value={100}>    <Foo/>  </countContext.Provider></div>}function Foo () {const value = useContext(countContext);return <div>{value}</div>}export default UseContext;

4.useEffect

让函数组件领有解决副作用的能力,相似生命周期函数。

1. useEffect 执行机会

  1. useEffect(() => {}): componentDidMount、componentDidUpdate
  2. useEffect(() => {}, []): componentDidMount
  3. useEffect(() => () => {}): componentWillUnMount

能够把 useEffect 看做 componentDidMount、componentDidUpdate、componentWillUnMount 这三个生命周期函数的组合。

import React, {useEffect, useState} from "react";function UseEffect() {  const [count, setCount] = useState(0);  // 组件挂载和组件更新时执行  // useEffect(() => {  //   console.log('run')  // });  // 组件挂载时执行  // useEffect(() => {  //   console.log('run')  // }, []);  // 革除上一个 effect  useEffect(() => {    console.log('1')    return () => {      console.log('run');    }  });  return <div>    <header>useEffect</header>    <span>{count}</span>    <button onClick={() => setCount(count + 1)}>plus</button>  </div>}export default UseEffect;

2. useEffect 应用形式

  1. 为window对象增加滚动事件
  2. 设置定时器让count数值每隔一秒减少1
import React, {useEffect, useState} from "react";import {root} from "../index";function UseEffect() {  function onScroll() {    console.log('页面滚动了');  }  // 挂载之后绑定事件  useEffect(() => {    window.addEventListener('scroll', onScroll)    return () => {      console.log('run')      window.removeEventListener('scroll', onScroll);    }  }, []);  const [count, setCount] = useState(0);  useEffect(() => {    const timer = setInterval(() => {      setCount(count => count + 1);    }, 1000)    return () => {      clearInterval(timer);    }  }, [])  useEffect(() => () => {    console.log('卸载组件');  })  return (    <div>      <header>useEffect</header>      <div>{count}</div>      <button onClick={() => root.unmount()}>卸载组件</button>    </div>  )}export default UseEffect;

3. useEffect 解决的问题

  1. 依照用处将代码进行分类(将一组相干的业务逻辑归置到了同一个副作用函数中)
  2. 简化反复代码,是组件外部代码更加清晰(在类组件中通常componentDidMount和componentDidUpdate中的代码是一样的)

4. useEffect 数据监测

只有指定数据发生变化的时候才会去触发 effect

const [count, setCount] = useState(0);const [num, setNum] = useState(0);// 只有count扭转的时候才会执行useEffect(() => {  document.title = count;}, [count]);return (  <div>    <header>useEffect</header>    <span>{count} {num}</span>    <button onClick={() => setCount(count => count + 1)}>addCount</button>    <button onClick={() => setNum(count => count + 1)}>addNum</button>  </div>)

5.useEffect 联合异步函数

useEffect中的参数函数不能是异步函数,因为useEffect函数要返回清理资源的函数,如果是异步函数就变成了返回Promise

useEffect(() => {  // 编写一个立刻执行函数  (async () => {    await axios.get()  })();})

6.useMemo

useMemo 的行为相似 Vue 中的计算属性,能够监测某一个值的变动,依据变动值计算新值。
useMemo 会缓存计算结果,如果监测值没有发生变化,即便组件从新渲染也不会从新计算,此行为能够有助于防止在每个渲染上进行低廉的计算。

import {useMemo} from 'react';// 挂载实现后默认执行一次,如果监听的count产生了变动则再次执行const result = useMemo(() => {  return result;}, [count]);

7.应用memo办法进步组件性能

性能优化,如果本组件中的数据没有产生任何变动,就阻止组件进行更新。相似类组件中的 PureComponent 和 shouldComponentUpdate

import React, {memo, useState} from "react";function Memo () {  const [count, setCount] = useState(0);  return <div>    <span>{count}</span>    <button onClick={() => setCount(count => count + 1)}>+1</button>    <Foo/>  </div>}// 每当 count + 1 Foo即便没有扭转也会被从新渲染// function Foo() {//   console.log('从新渲染');//   return <div>Foo组件</div>// }// 这样 Memo 组件更新就不会连带 Foo 更新const Foo = memo(() => {  console.log('从新渲染');  return <div>Foo组件</div>})// 当Memo没有产生数据变动且父组件产生了变动,Memo组件不会被更新export default memo(Memo);

8.useCallback

性能优化,缓存函数,使组件从新渲染时失去雷同的函数实例。

Foo 子组件被从新渲染

import React, {useCallback, useState, memo} from 'react';function UseCallback() {  const [count, setCount] = useState(0);  function resetCount () {    setCount(0);  }  return <div>    <span>{count}</span>    <button onClick={() => setCount(count => count + 1)}>+1</button>    <Foo resetCount={resetCount}/>  </div>}const Foo = memo((props) => {  /**   * 当 count 产生扭转时会更新父组件,父组件中的 resetCount 函数被从新定义   * 这时的 resetCount 与 Foo props中的 resetCount 实例曾经产生了扭转,所以 Foo 会被从新渲染   */  console.log('从新渲染');  return <div>Foo组件    <button onClick={props.resetCount}>resetCount</button>  </div>})export default UseCallback;

// 应用 useCallback 优化 resetCount

const resetCount = useCallback(() => setCount(0), [setCount]);

9.useRef

1.获取DOM对象

import React, {useRef} from 'react';function UseRef () {  const userName = useRef();  const handler = () => console.log(userName.current);  return <div>    <header>UseRef</header>    <input ref={userName} onChange={handler}/>  </div>}export default UseRef;

2.useRef保留数据

即便组件从新渲染,保留的数据依然还在,保留的数据被更改不会触发组件渲染(跨组件周期)。

// 保留数据  const [count, setCount] = useState(0);  // 即便组件更新并不会重置,所以 stopCount 能够拿到 timer  let timer = useRef();  useEffect(() => {    timer.current = setInterval(() => {      setCount(count => count + 1);    }, 1000);  }, [])  const stopCount = () => {    clearInterval(timer.current);  }  return <div>    <header>useRef</header>    <span>{count}</span>    <button onClick={stopCount}>stop</button>  </div>

10.自定义hook

  • 自定义hook是规范的封装和共享逻辑的形式
  • 自定义hook是一个函数,其名称以use结尾
  • 自定义hook其实就是逻辑和内置hook的组合
import React, {useEffect, useState} from 'react';import axios from 'axios';/** * 以后组件须要再组件挂载实现之后,获取文章数据 * 假如获取文章数据的申请是共享逻辑,尝试把它写到自定义hook当中 */// 创立自定义hookfunction useGetEssay () {  const [essay, setEssay] = useState({});  useEffect(() => {    axios.get('https://www.fastmock.site/mock/eefbb8ce7302645510629510865adb64/api/essay')      .then(res => setEssay(res.data));  }, []);  return [essay, setEssay];}function CustomizationHook () {  const [essay, setEssay] = useGetEssay(null);  return <div>    <header>CustomizationHook</header>    <div>      <p>{essay.title}</p>      <div>{essay.content}</div>    </div>  </div>}export default CustomizationHook;
  1. 提取表单中的罕用逻辑

    function useUpdateInput(initialValue) {  const [value, setValue] = useState(initialValue);  return { value, onChange: e => setValue(value => value = e.target.value)  }}function CustomizationHook() { const userNameInput = useUpdateInput(''); const passwordInput = useUpdateInput(''); const submitForm = e => {  e.preventDefault();console.log(userNameInput.value, passwordInput.value) }  return <form onSubmit={submitForm}> {/*为了绑定数据,每个表单元素都须要设置 value 和 onChange,这就属于共享逻辑*/} <input type="text" name="username" {...userNameInput} /> <input type="text" name="password" {...passwordInput} /> <input type="submit"/>  </form>}