一文带你熟悉React-Hooks

209 阅读5分钟

1、引导

Hook是React16.8⼀一个新增项,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

2、Hooks的特点:

  • 使你在无需修改组件结构的情况下复⽤状态逻辑
  • 可将组件中相互关联的部分拆分成更⼩的函数,复杂组件将变得更容易理解
  • 更简洁、更易理解的代码

3、常用hooks

3.1 useState

import React, { useState } from 'react'

function App() {
  const [count, setCount] = useState(0) // ES6解构语法
    
  return (
    <div>
      <h1>you click {count} times</h1>
      <input type="button" onClick={()=> setCount(count + 1)} value="click me" />
    </div>
  ) 
}

export default App

每次点击按钮时,setCount 方法接收的 count 都是最新状态,之后 React 会重新渲染组件并保留新的状态。

如果一个组件需要多个状态,我们可以在组件中多次使用 useState

3.2 useEffect

useEffect 给函数组件增加了执行副作用操作的能力。

副作⽤(Side Effect)是指⼀个 function 做了和本身运算返回值⽆关的事,⽐如:修改了全局变量、修改了传⼊的参数、甚⾄是 console.log(),所以 ajax 操作,修改 dom 都是算作副作⽤。

import React, { useEffect, useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
  useEffect(() => {
    // 模拟发送请求信息,在一秒后改变 count 的值
    setTimeout(() => {
      setCount(10)
    }, 1000)
  })
    
  return (
    <div>{count}</div>
  ) 
}

export default App

测试会发现副作⽤操作会被频繁调⽤。

  • 设置依赖,让只执行一次副作用,如果副作⽤操作对某状态有依赖,务必添加依赖选项。
 // 设置空数组意为没有依赖,则副作⽤操作仅执行⼀一次 
 useEffect(()=>{...}, [])
  • 清除⼯作:有⼀些副作用是需要清除的,清除⼯作非常重要的, 可以防⽌引起内存泄露。
useEffect(() => {
    const timer = setInterval(() => {
	    console.log('msg'); 
    }, 1000);
  return function(){
       clearInterval(timer);
	}
}, []);

组件卸载后会执行返回的清理理函数。

3.3 useReducer

useReducer是useState的可选项,常⽤于组件有复杂状态逻辑时,类似于redux中reducer概念。

const initialState = {count: 0};
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    default:
      throw new Error()
  }
}

import React, { useReducer } from 'react'

function App() {
  const [state, dispatch] = useReducer(reducer, initialState)
    
  return (
    <div>
      <h1>you click {state.count} times</h1>
      <input type="button" onClick={()=> dispatch({type: 'increment'})} value="click me" />
    </div>
  ) 
}

export default App

以下场景,你可以优先使用 useReducer

  • state 变化很复杂,经常一个操作需要修改很多 state
  • 深层子组件里去修改一些状态;
  • 应用程序比较大,UI 和业务需要分开维护。

3.4 useContext

context 做的事情就是创建一个上下文对象,并且对外暴露提供者和消费者,在上下文之内的所有子组件,都可以访问这个上下文环境之内的数据,并且不用通过 props。 简单来说, context 的作用就是对它所包含的组件树提供全局共享数据的一种技术。

import React, { useContext } from "react"; 
const Context = React.createContext();
const Provider = Context.Provider;
export default function HookContext() { 
  const store = { userName: "xiaoming" };
  return (
    <div>
      <h1>HookContext ⻚页⾯面</h1> 
      <Provider value={store}>
        <Child />
      </Provider>
    </div>
	); 
}

function Child(props) {
   const { userName } = useContext(Context); 
   return (
     <div>
      <div>userName: {userName}</div>
    </div>
	);
}

3.5 useMemo & useCallback

useCallback 和 useMemo 的第一个参数是一个执行函数,第二个参数可用于定义其依赖的所有变量。如果其中一个变量发生变化,则 useMemo 或者 useCallback 会运行。这两个 API 都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,并且这两个 Hooks 都返回缓存的值,useMemo 返回缓存的变量, useCallback 返回缓存的函数。 ​

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
)

3.5.1 useMemo

通过使用 useMemo,我们可以将上一次执行的结果缓存在变量中,只有在 count 值变化时进行更新执行。

function WithoutMemo() {
  const [count, setCount] = useState(1)
  const [val, setValue] = useState('')

  const expensive = useMemo(() => {
    console.log('compute')
    // 假设是个计算量比较大的函数
    return count * 100
  }, [count])

  return (
    <div>
      <h4>
        {count}-{val}-{expensive}
      </h4>
      <div>
        <button onClick={() => setCount(count + 1)}>+c1</button>
        <input value={val} onChange={event => setValue(event.target.value)} />
      </div>
    </div>
  )
}

3.5.2 useCallback

当我们父组件中包含一个子组件,子组件接收一个函数作为 props,通常的情况是,如果父组件更新,那么子组件也会随之更新。但我们知道,大多数情况下子组件的更新是没有必要的,这时我们可以借助 useCallback 来解决这个问题,通过 useCallback 返回的函数,然后把这个缓存的函数传递给子组件,这样只有当这个函数发生变化时,子组件才会更新。一起来看下例子:

// 子组件
function SubComponent(props){
    console.log('SubComponent render');
    return (
        <button onClick={props.onClick}>{props.data.count}</button>
    )
}
// 父组件
const SubComponent = memo(SubComponent);

export default function WithMemo(){
    console.log('Counter render');
    const [val, setValue]= useState('计数器');
    const [count,setCount] = useState(0);

    const data = useMemo(()=>({count}),[count]);
    
    // 有没有后面的依赖项数组很重要,否则还是会重新渲染
    const addClick = useCallback(()=>{
        setCount(count+1);
    }, [count]);

    return (
        <>
           <input value={val} onChange={event => setValue(event.target.value)} />
            <SubComponent data={data} onClick={addClick}/>
        </>
    )
}

3.6 useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的 ref 对象都是同一个。

function ChildComponent() {
  const inputEl = useRef(null)
  const curFocus = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus()
  }
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={curFocus}>Focus the input</button>
    </>
  )
}

3.7 useLayoutEffect

其函数签名与 useEffect 相同,useEffect 是在全部渲染完之后才会执行,而 useLayoutEffect 会在浏览器布局之后,绘制之前执行。useLayoutEffect 是在页面绘制之前执行,因此会阻塞视图更新。

function LayoutEffect() {
  const [color, setColor] = useState("red")
  useLayoutEffect(() => {
    alert(color)
  })
  useEffect(() => {
    console.log('color', color)
  })
  return (
    <>
      <div id="myDiv" style={{ background: color }}>
        颜色
      </div>
      <button onClick={() => setColor('red')}>红</button>
      <button onClick={() => setColor('yellow')}>黄</button>
      <button onClick={() => setColor('blue')}>蓝</button>
    </>
  )

3.8 自定义hooks

 Hooks 代码还可以封装起来,变成一个自定义的 Hook,便于共享。

const usePerson = (personId) => {
  const [loading, setLoading] = useState(true);
  const [person, setPerson] = useState({});
  useEffect(() => {
    setLoading(true);
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then(response => response.json())
      .then(data => {
        setPerson(data);
        setLoading(false);
      });
  }, [personId]);  
  return [loading, person];
}
  
  
  const Person = ({ personId }) => {
  const [loading, person] = usePerson(personId);

  if (loading === true) {
    return <p>Loading ...</p>;
  }

  return (
    <div>
      <p>You're viewing: {person.name}</p>
      <p>Height: {person.height}</p>
      <p>Mass: {person.mass}</p>
    </div>
  );
};

4、总结

本篇文章通过对React-hooks常用api的讲解来让大家全面熟悉hooks,相信你今后在写函数组件时能做到游刃有余.