React Hook 使用指南

305 阅读8分钟

我这个人不喜欢说这么多花里胡哨的 直接上代码,都是个人比较收录,有问题评论区受教了大家 >v<

什么是React Hook

  • React 一直都提倡使用函数组件,但是有时候需要使用 state 或者其他一些功能时,只能使用类组件,因为函数组件没有实例,没有生命周期函数,只有类组件才有
  • Hooks 是 React 16.8 新增的特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
  • Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。稍后我们将看到,Hook 还提供了一种更强大的方式来组合他们。
  • 凡是 use 开头的React Hook的API

Hook比类好在哪?

  • 不需要this

    • 为了保证this的指向正确,我们要经常写这样的代码:
        - this.handleClick = this.handleClick.bind(this),
    - 或者是这样的代码:<button onClick={() => this.handleClick(e)}>。
    
    • 一旦我们不小心忘了绑定this,各种bug就随之而来,很麻烦。
  • 简化类组件的生命周期,使其不这么复杂

    • 我们通常希望一个函数只做一件事情,但我们的生命周期钩子函数里通常同时做了很多事情。
    • 比如我们需要在componentDidMount中发起ajax请求获取数据,绑定一些事件监听等等。同时,有时候我们还需要在componentDidUpdate做一遍同样的事情。当项目变复杂后,这一块的代码也变得不那么直观。
  • 能在无需修改组件结构的情况下复用状态逻辑(自定义 Hooks )

  • 副作用横切关注点,由useEffect和useLayoutEffect解决,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等

  • 解决类组件的状态复用问题

    • 你在大型的工作项目中用react,你会发现你的项目中实际上很多react组件冗长且难以复用。尤其是那些写成class的组件,它们本身包含了状态(state),所以复用这类组件就变得很麻烦,官方给出渲染属性(Render Props)和高阶组件两种解决方式。
    • 而这两种方式会增加我们代码的层级关系,而HOOK就比较简单

我们看看hook复用性

//useHook.jsx
import React, { useState } from "react";

function useHook(item) {
  const [status, setStatus] = useState("穿鞋子");

  setTimeout(() => {
    setStatus("穿好鞋子");
  }, item.time);

  return (
    <h1 key={item.id}>
      {status === "穿鞋子"
        ? `${item.name}正在穿鞋子...`
        : `${item.name}已经穿好鞋子了`}
    </h1>
  );
}

export default useHook;

import React from "react";
import useHook from "./useHook";//哦吼

function Page() {
  const data = [
    { id: 1, name: "小A", time: "2000" },
    { id: 2, name: "小B", time: "5000" }
  ];
  return (
    <div>
      {data.map(item => {
        return useHook(item);
      })}
    </div>
  );
}
export default Page;

useState

useState是react自带的一个hook函数,它的作用就是用来声明状态变量。

  • useState这个函数接收的参数是我们的状态初始值(initial state)

  • 它返回了一个数组,

    • 第[0]项是当前当前的状态值,不填为undefiend

    • 第[1]项是可以改变状态值的方法函数。

import { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);
 // count --> 0
 // setCount(count=>count+100)
 //count --> 100
  • 数组形式
// const [list, setList] = useState([]);

 <button onClick={() => {
       list.splice(0, 1)
       setList([...list])
 }}>Push</button>
  • 对象形式
const [obj, setObj] = useState({
        a:1,b:2
    })
    
<button onClick={() => setObj({
                ...obj,
                b:22222
})}>obj</button>
  • 获取setState更新后的值
 setNumber(number + 1, () => { 
    console.log(number); 
    }) 

useState注意的细节

  • useState最好写到函数的起始位置,便于阅读

  • useState严禁出现在代码块(判断、循环)中

  • useState返回的函数(数组的第二项),引用不变(节约内存空间)

  • 使用函数改变数据,若数据和之前的数据完全相等不会导致重新渲染,以达到优化效率的目的)

  • 使用函数改变数据,传入的值不会和原来的数据进行合并,而是直接替换。(setState是混合)

  • const [, forceUpdate] = useState({}); 调用函数 forceUpdate({}) 传入空对象 强制刷新

  • 每次渲染都是独立的闭包,每一次渲染都有它自己的 Props 和 State 每一次渲染都有它自己的事件处理函数

useEffect 副作用钩子

为函数组件增加了操作副作用的能力。

它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API

  • useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行
    • 该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容
  • 该函数接收一个数组,为依赖值,值有变化立即触发函数

useEffect有多种形式 我们来瞅瞅 0.0

  • 默认不传参,首次渲染不执行,每次有数据重新渲染时会促发Effect
    useEffect(() => {
        console.log(123)
    });
  • 传入一个空数组 , 可以理解为等同于componentDieMount和componentDidUpdate
  useEffect(() => {
        console.log(123)
    }),[];
  • 依赖性变化,会触发Effect
    • 首次渲染触发一次
    • 依赖项变化再次触发
 useEffect(() => {
        console.log(123)
    },[number,number2]);
//number和number2其一变化 即打印123
  • 组件卸载时,会执行return清理函数
useEffect(()=>{
    let $timer = setInterval(()=>{
        setNumber(number=>number+1);
    },1000);
    return ()=>{ //组件切换的时候 停止定时器
        clearInterval($timer)
    }
});

useLayoutEffect

  • useEffect:浏览器渲染完成后,render结束后就运行;

  • useLayoutEffectHook:Dom一更新就执行,此时还没有render,比effect早一步

useMemo

对值进行缓存,性能优化

  const [content, setContent] = useState('内容')
  const [name, setName] = useState('名称')
 // const otherName = changeName(name) //会被其他数据影响
  const otherName = useMemo(()=>changeName(name),[name])//只有name更新了才会影响到
  function changeName(name) {
    console.log('11')
    return name + '改变name的方法'
}
return (
    <>
        <button onClick={() => setName(new Date().getTime())}>name</button>
        <button onClick={() => setContent(new Date().getTime())}>content</button>
        {content} {otherName}
    </>
)
  • 使用memo,只有name有改变立马触发changeName进行实时渲染,其他事件不会影响触发该函数
  • 如果不使用memo其他人可以通过其他事件触发该函数。

useCallback

缓存函数,性能优化,只要依赖项没有发生变化,则始终返回之前函数的地址

const [message, setMessage] = useState('hello world.');

const handleChange = useCallback((value) => {
    setMessage(value);
}, []);

useReducer

如果代码稍微复杂一些,可以不使用useState,用useReducer。

使用过redux或者vuex应该都很熟悉把

  • action是改变数据的唯一原因(本质上就是一个对象,action有两个属性) -type:字符串,动作的类型 -payload:任意类型,动作发生后的附加信息
  • reducer函数用来改变数据 -接收2个参数
    • state:表示当前数据仓库中的数据
    • action:描述了如何去改变数据,以及改变数据的一些附加信息 - -
    • reducer必须是纯函数,不能有任何副作用
  • dispatch用来促触发reducer
    • 该函数仅接收一个参数:action
    • 该函数会间接去调用reducer,以达到改变数据的目的
import React from 'react';
import { useState, useContext, useReducer } from 'react'
const initialState = {
  a: 1,
  b: 2,
  number: 0
};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { number: state.number + 1 };
    case 'decrement':
      return { number: state.number - 1 };
    default:
      throw new Error();
  }
}

export default function Test() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      你好
     Count: {state.number}  a:{state.a} b:{state.b}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  )

useContext

createContext创建上下文传递数据 useContext接受上下文的数据

//父组件
import Test from './component/Test'
import React, {createContext} from 'react'
const ctx = React.createContext();
export default function App() {
    return (
        <div>
            <ctx.Provider value="abc">
                <Test />
            </ctx.Provider>
        </div>
    )
}
//子组件,
可以将value传入给子组件(孙组件)
function Test() {
    const value = useContext(ctx);
    return <h1>Test,上下文的值:{value}</h1> //abc
}

使用 useContext+useReducer 可以搭建一个迷你的redux

useRef

类组件、React 元素用 React.createRef,函数组件使用 useRef

  • useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数(initialValue)
export default function Counter() {
    const childRef = useRef(null);
    const inputRef = useRef(null);
    useEffect(()=>{
        console.log(childRef) //div
    },[])
    
    return (
    <>
        <input ref={inputRef}></input>
        <button onClick={() => {
            console.log(inputRef.current.value)//input当前输入框的值
        }}>点我获取ref</button>
        <div ref={childRef}></div>
    </>
)
}

自定义hook

可以理解为HOC

import React, { useLayoutEffect, useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

function useNumber(){
  let [number,setNumber] = useState(0);
  useEffect(()=>{
    setInterval(()=>{
        setNumber(number=>number+1);
    },1000);
  },[]);
  return [number,setNumber];
}
// 每个组件调用同一个 hook,只是复用 hook 的状态逻辑,并不会共用一个状态
function Counter1(){
    let [number,setNumber] = useNumber();
    return (
        <div><button onClick={()=>{
            setNumber(number+1)
        }}>{number}</button></div>
    )
}
function Counter2(){
    let [number,setNumber] = useNumber();
    return (
        <div><button  onClick={()=>{
            setNumber(number+1)
        }}>{number}</button></div>
    )
}
ReactDOM.render(<><Counter1 /><Counter2 /></>, document.getElementById('root'));

上一个老八蜜汁小汉堡 useXState

const useXState = (initState) => {
    const [state, setState] = useState(initState)
    let isUpdate = useRef()
    const setXState = (state, cb) => {
        isUpdate.current = cb
        setState(state)
    }
    useEffect(() => {
      if (isUpdate.current) {
        isUpdate.current();//执行cb !!!
        isUpdate.current = null;
      }
    }, [state]);
    return [state, setXState]
    }
  • 之后我们修改对象时就可以使用回调打印最新值了 (useState的话会报错,会教你用useEffect监听)
const [val, setVal] = useXState({ a: 1, b: 2 });
return(
    <>
        {val.a}
        <button onClick={() => {
            setVal({...val,a:2333},()=>{
                console.log(val)
            } )
        }}>AAA</button>
    </>
)

useHook 就到这里了 x.x 前端小白一个 希望大佬发现问题能大胆的说我会吸取其精