ReactHooks学习笔记

358 阅读7分钟

概述

要求react版本必须是16.8以上,React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。

使用

使用规则

1、只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useStateuseEffect 调用之间保持 hook 状态的正确。

2、只在 React 函数中调用 Hook

**不要在普通的 JavaScript 函数中调用 Hook。**你可以:

  • ✅ 在 React 的函数组件中调用 Hook

  • ✅ 在自定义 Hook 中调用其他 Hook

遵循此规则,确保组件的状态逻辑在代码中清晰可见。

例子

import { React, useState } from "react";
export default function Test2() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>count的值是{count}</h1>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        点我count+1
      </button>
    </div>
  );
}

最简单的count+1的例子

四大钩子——useState

useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。

useState()这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是count),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是set前缀加上状态的变量名(上例是setCount)。

上面例子已经做了🌰了

声明多个state

function ExampleWithManyStates() {
  // 声明多个 state 变量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '学习 Hook' }]);

与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

const [state, setState] = useState({});
setState(prevState => {
  // 也可以使用 Object.assign
  return {...prevState, ...updatedValues};
});

惰性初始 state

initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

四大钩子——useEffect

useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在

componentDidMountcomponentDidUpdatecomponentWillUnmount里面的代码,现在可以放在useEffect()

useEffect(()  =>  {
  // Async Action
}, [dependencies])

上面用法中,

useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()

import { React, useEffect, useState } from "react";
export default function Test2() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("count----", count);
  }, [count]);
  return (
    <div>
      <h1>count的值是{count}</h1>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        点我count+1
      </button>
    </div>
  );
}

可以看到一打开页面就看到输出了count---- 0,然后没有加1的时候又输出了count的值,

**useEffect** 会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后_和_每次更新之后都会执行。

  useEffect(() => {
      //可以在这里发axios请求、订阅事件
    console.log("count----", count);
    return function cleanUp(){
        //在这里关闭axios请求、关闭订阅事件
        console.log('Hello');
    }
  }, [count]);

为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

React 何时清除 effect? React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

虽然 useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。在开始新的更新前,React 总会先清除上一轮渲染的 effect。

四大钩子——useContext

useContext——共享状态钩子

如果需要在组件之间共享状态,可以使用useContext()

现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。

*

第一步就是使用 React Context API,在组件外部建立一个 Context。

const AppContext = React.createContext({});

组件封装代码如下。

*

上面代码中,

AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享。

Navbar 组件的代码如下。

const Navbar = () => {
  const { username } = useContext(AppContext);
  return (
    <div className="navbar">
      <p>AwesomeSite</p>
      <p>{username}</p>
    </div>
  );
}

上面代码中,

useContext()钩子函数用来引入 Context 对象,从中获取username属性。

四大钩子——useReducer

useReducer():action 钩子

Reducer 函数的形式是(state, action) => newStateuseReducers()钩子用来引入 Reducer 功能。

const [state, dispatch] = useReducer(reducer, initialState);

它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。

import { React, useReducer } from "react";
const myReducer = (state, action) => {
  switch (action.type) {
    case "countUp":
      return {
        ...state,
        count: state.count + 1,
      };
    case "countDown":
      return {
        ...state,
        count: state.count - 1,
      };
    default:
      return state;
  }
};
export default function Test3() {
  const [state, dispatch] = useReducer(myReducer, { count: 0 });
  return (
    <div>
      <button onClick={() => dispatch({ type: "countUp" })}>+1</button>
      <button onClick={() => dispatch({ type: "countDown" })}>-1</button>
      <p>Count: {state.count}</p>
    </div>
  );
}

补充

useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

ref对象的值发生改变之后,不会触发组件重新渲染。

为什么需要useRef

  1. 函数组件访问DOM元素;

  2. 函数组件访问之前渲染变量。

函数组件每次渲染都会被执行,函数内部的局部变量一般会重新创建,利用useRef

可以访问上次渲染的变量,类似类组件的实例变量效果。

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

useMemo

useMemomemo 作用相同,都是用来做性能优化的,不会影响业务逻辑。

memo函数,针对的是一个组件的渲染,是否重复执行。(<Foo/>)

useMemo,定义了一段函数逻辑,是否重复执行。(() => {})

useMemo的第一个参数是函数,第二个参数一般都为数组

如果不传第二个参数,与useEffect类似,意味着每一次都会执行第一个函数参数,那么使用useMemo的意义就没有了。

如果第二个参数传的是空数组[],与useEffect类似,只执行一次。

useMemouseEffect有不一样的一点就是调用时机 —— useEffect执行的是副作用,所以一定是在渲染之后运行的;而useMemo是需要有返回值的,返回值会参与渲染,所以useMemo是是在渲染期间完成的。

import React, { useState, useMemo } from 'react'; 
function App() { 
    const [count, setCount] = useState(0); 
    const double = useMemo(() => { 
            return count * 2 
        }, [count === 3]) 
    return ( 
        <div> 
        <button onClick={() => setCount(count + 1)}>点我加一</button> 
        <h1>count: {count}</h1> 
        <h1>double: {double}</h1> 
        </div> 
    ) } 
export default App;

这里使用了count === 3这个表达式的结果(布尔值,true/false),作为是否重新渲染Double组件的判断依据。

所以随着count从0开始的每一次的加一,Double组件最终也只会重新渲染两次:

  • 当count值为3的时候,count === 3这个表达式的结果为true,发生改变了。

  • 当count值为4的时候,count === 3这个表达式的结果为false,发生改变了。

useMemo中依赖的值也可以是useMemo

    import React, { useState, useMemo } from "react";

    function App() {
      const [count, setCount] = useState(0);

      const double = useMemo(() => {
        return count * 2;
      }, [count === 3]);

      const half = useMemo(() => {
        return count / 2;
      }, [double]);

      return (
        <div>
          <button onClick={() => setCount(count + 1)}>点我加一</button>
          <h1>
            count: {count} double: {double} half: {half}
          </h1>
        </div>
      );
    }

    export default App;

useCallback

为什么要有useCallback这个钩子呢

1.useCallback会返回一个函数的memoized(记忆的)值

2.在依赖不变的情况下,多次定义的时候,返回的值是相同的

当我们修改父组件里面值的时候,子组件肯定也会重新渲染,但是我们不想它重新渲染,怎么做呢?

import React, { useState, useCallback,memo } from 'react'

const HYButton = memo((props)=>{
  console.log(props.title)
  return <button onClick={props.increament}>HYButton+1</button>
})

export default function UseCallbackDemoOne() {

  const [count,setCount] = useState(0)
  const [show,setShow] = useState(true)
   
  const  increament1 = () => {
    console.log('执行increament1')
    setCount(count + 1)
  }
  
  const  increament2 = useCallback(()=>{
    console.log('执行increament2')
    setCount(count + 1)
  },[count])
  

  return (
    <div>
      <h2>{count}</h2>
      <HYButton title="btn1" increament={increament1}/>
      <HYButton title="btn2" increament={increament2}/>

      <button onClick={e=>setShow(!show)}>切换</button>
    </div>
  )
}

当点击切换时,可以看到btn1重新渲染了,而btn2并没有,因为increament2依赖的count没有改变,所以每次返回都是相同的值,所以btn2就不会重新渲染