课程目标
- 掌握函数组件的创建与使用;
- 掌握 React 常见 Hooks 使用;
- 掌握如何自定义 Hook。
课程内容
函数式组件
- 函数式组件,本质就是一个常规函数,接收一个参数 props 并返回一个 reactNodes,即 return 该组件需要输出的视图;
- 函数式组件中没有 this 和生命周期函数;
- 使用函数式组件时,应该尽量减少在函数中声明子函数,否则组件每次更新时都会重新创建这个函数。
React Hooks(钩子)
-- React Hooks 是 React 16.8 中的新增功能,它们使您无需编写类即可使用状态和其它 React 功能。
常用 Hook
useState
const [state, setState] = useState(initialState);
- const [状态,修改该状态的方法] = useState(初始值);
- 在同一组件中可以使用 useState 定义多个状态;
- 注意 useState 返回的 setState 方法不会进行对象的浅合并,即 setState 方法只接收一个参数,该参数代表的就是更新完之后的新状态;
- 会受到批处理影响,多次调用 setState 会被合并只执行一次;
- 注意 useState 返回的 setState 方法同样是异步方法。
import { useState } from "react"; function Child(props) { const [count, setCount] = useState(5); const [num, setNum] = useState(0) console.log('render'); return ( <>
Child:{props.info}
Count: {count}
Num: {num}
> ); } export default Child; - 只在 React 函数中调用 Hook:
- React 函数组件中;
- React 自定义 Hook 中;
- 只在最顶层使用 Hook:在 React 中要保证 hook 的执行顺序在组件更新前后一致,否则会报错
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render
,原因且看下面 hook 实现原理:// useState 实现原理初版
Document // hook 为什么要保持调用顺序
Document
useEffect
- 可以完成的作用等同于类组件中的生命周期函数:componentDidMount、componentDidUpdate 和 componentWillUnmount;
- 需要清除的副作用。
// 基本使用 import { useEffect, useState } from "react"; function Effect() { /** * useEffect 副作用钩子:用于在 React 函数中来处理副作用【数据请求、DOM 操作】 useEffect( () => { // effect 函数 return () => { // cleanUp 函数, 可选 } }, [依赖数据]) // 依赖函数也是可选的 挂载阶段: 从上向下一行行执行代码,如果碰到 useEffect,则将对应的 effect、cleanUp 函数分别存储到一个队列中,当组件挂在完成后,按添加顺序执行 effect 函数;cleanUp 函数不执行; 更新阶段: 从上向下一行行执行代码,如果碰到 useEffect,则将对应的 effect 函数存储到一个队列中;当组件更新完成之后,找到之前存储的 cleanUp 队列,依次执行;然后再按照添加顺序依次执行 effect 队列、获取 cleanUp 函数并存储到一个队列中;如果有依赖参数,则对应的依赖参数发生变化才会执行更新阶段的代码;如果没有依赖参数,则只要组件有更新就会执行更新阶段代码; 卸载阶段: 找到对应的 cleanUp 队列,依次执行。 依赖参数:(只对更新阶段有影响) 1、不写依赖参数 useEffect(()=>{}),组件有更新,就会执行 cleanUp 函数 和 effect 函数; 2、有具体依赖参数(1-多个)useEffect(()=>{},[a[,b,...]),则组件更新时,其依赖参数有变化,会执行其 cleanUp 函数和 effect 函数; 3、有具体依赖参数(0个)useEffect(()=>{},[]),则组建更新时,不会执行其 cleanUp 和 effect 函数,只有加载完成和卸载阶段会执行这里的代码。 */ const [count, setCount] = useState(5); useEffect( () => { console.log('effect-1'); return () => { console.log('cleanUp-1'); } }); useEffect( () => { console.log('effect-2'); return () => { console.log('cleanUp-2'); } }); console.log('render'); return ( <>
Count: {count}
> ); } export default Effect; - 和类组件的生命周期进行对应:能对应到的生命周期如下
- 挂在完成之后,做某事;
- 更新完成之后,做某事;
- 挂载完成和更新完成之后,做某事;
- 即将卸载时,做某事。
import { useEffect, useRef } from "react"; function EffectLife() { useEffect( () => { console.log('2--挂载完成之后'); return () => { console.log('即将卸载时做某事'); } }, []); useEffect( () => { console.log('3--更新、挂载完成之后都会做某事'); }); // 只在更新阶段做某事【需要使用到 useRef】 const isMount = useRef(false); useEffect( () => { if(isMount.current) { console.log('更新阶段做某事'); } else { isMount.current = true; } }) console.log('1--render'); return ( <>
Effect Life
> ); } export default EffectLife;
useRef
- 用户关联原生 DOM 节点;
- 或者用来记录组件更新前的一些数据。
import { useEffect, useRef, useState } from "react"; function RefTest() { // const ref = useRef(init); // 1.关联节点实例 // 2.传递组件更新前的一些数据,当 useRef 中存储的是某项数据时,该数据并不会随着组件的更新而自动更新 const [count, setCount] = useState(5888); const ref = useRef(count); const countRef = useRef(); useEffect( () => { ref.current = count; console.log(ref, countRef.current.innerHTML); }) return ( <>
Count: {count}> ); } export default RefTest;
useMemo
- const data = useMemo(cb, []); 类似于 useState,有一个依赖参数。
// 发现做任何操作,都会执行 maxValue 函数 import { useState } from "react"; function MemoTest() { const [count, setCount] = useState(1); const [num, setNum] = useState(5); const [val, setVal] = useState(''); const plusCount = () => { console.log('plusCount'); setCount(count+1); }; const minusCount = () => { console.log('minusCount'); setCount(count-1); }; const plusNum = () => { console.log('plusNum'); setNum(num+1); }; const minusNum = () => { console.log('minusNum'); setNum(num-1) }; const valueChange = ({target}) => { console.log('valueChange'); setVal(target.value) }; const maxValue = () => { console.log('maxValue'); return count>num?"count":"num"; }; return ( <>
MemoTest
Count: {count}; ,Num: {num}; ,当前比较大的值为:{maxValue()}
{val}
> ); } export default MemoTest;import { useMemo, useState } from "react"; /** * useMemo(cb, []) * 不是函数,直接等于 cb 的返回值。 * 发现只有修改 count、num 的值才会打印 maxValue,在输入框中输入内容时不再打印 */ function MemoTest() { const [count, setCount] = useState(1); const [num, setNum] = useState(5); const [val, setVal] = useState(''); const plusCount = () => { console.log('plusCount'); setCount(count+1); }; const minusCount = () => { console.log('minusCount'); setCount(count-1); }; const plusNum = () => { console.log('plusNum'); setNum(num+1); }; const minusNum = () => { console.log('minusNum'); setNum(num-1) }; const valueChange = ({target}) => { console.log('valueChange'); setVal(target.value) }; const maxValue = useMemo(() => { console.log('maxValue'); return count>num?"count":"num"; }, [num, count]); return ( <>
MemoTest
Count: {count}; ,Num: {num}; ,当前比较大的值为:{maxValue}
{val}
> ); } export default MemoTest;
useCallback
- 是 useMemo 的一个进化体;
- 当 useMemo 返回一个函数时,可以使用 useCallback 来改进;
- 将上面的代码进行改进,修改 plusCount,只有在 count 进行更新时才会进行声明子函数:
import { useCallback, useMemo, useState } from "react"; /** * useMemo(cb, []) * 当 useMemo 返回值是一个函数时,可以使用 useCallback 来优化 */ function CallbackTest() { const [count, setCount] = useState(1); const [num, setNum] = useState(5); const [val, setVal] = useState(''); const plusCount = useCallback( () => { console.log('plusCount'); setCount(count+1); }, [count]); const minusCount = useCallback(() => { console.log('minusCount'); setCount(count-1); }, [count]); const plusNum = useCallback(() => { console.log('plusNum'); setNum(num+1); }, [num]); const minusNum = useCallback(() => { console.log('minusNum'); setNum(num-1) }, [num]); const valueChange = useCallback(({target}) => { console.log('valueChange'); setVal(target.value) }, [val]); const maxValue = useMemo(() => { console.log('maxValue'); return count>num?"count":"num"; }, [num, count]); return ( <>
MemoTest
Count: {count}; ,Num: {num}; ,当前比较大的值为:{maxValue}
{val}
> ); } export default CallbackTest; - 使用 useCallback、useMemo,会更麻烦一点,但是性能会有很大提升。
Memo
- React.memo(Component, [areEqual(prevProps, nextProps]);
Hook 使用规则
- 只在 React 函数中调用 Hook;
- React 函数组件中;
- React 自定义 Hook 中;
- 只在最顶层使用 Hook。
自定义 Hook
- 常用来解决(与状态相关的)逻辑复用问题;
- 自定义 hook 就是一个普通函数,但是该函数必须以 use 开始命名;
import { useCallback, useMemo, useState } from "react"; function useCount(init) { const [count, setCount] = useState(init); const plusCount = useCallback(() => { setCount(count + 1); }, [count]); const minusCount = useCallback(() => { setCount(count - 1); }, [count]); return [count, plusCount, minusCount]; } function HookTest() { const [count, plusCount, minusCount] = useCount(1); const [num, plusNum, minusNum] = useCount(5); const [val, setVal] = useState(''); const valueChange = useCallback(({target}) => { console.log('valueChange'); setVal(target.value) }, [val]); const maxValue = useMemo(() => { console.log('maxValue'); return count>num?"count":"num"; }, [num, count]); return ( <>
MemoTest
Count: {count}; ,Num: {num}; ,当前比较大的值为:{maxValue}
{val}
> ); } export default HookTest;