第十节: React Hooks(持续更新)

React官方推荐使用钩子(Hooks)而不是高阶组件或者render props来复用状态逻辑。解释说函数组件比类组件更简洁,易于理解和调试,而且能够确保组件之间的渲染逻辑保持同步。
React Hooks在React16.8版本之后出现的。之前还是使用类组件更方便写,因为之前的函数组件无法维护内部状态。

有哪些Hooks?

下面,我们就来先介绍下React的Hooks有哪些?

  1. 组件状态处理相关: useState、useReducer、useContext;
  2. 处理副作用: useEffect、useLayoutEffect;
  3. 性能优化相关:useMemo、useCallback;
  4. DOM相关: useRef
  5. redux相关: useSelector、useDispatch、useStore
  6. 用户自定义hook或第三方库带的hook等

useState

在函数组件保存数据的主要方法

前面章节有详细介绍了useState的用法——快速跳转

useEffect

类组件通常在生命周期中执行需要的操作,useEffect的作用,是补充函数组件无法正确执行副作用的问题。
React中的副作用,是指在组件生命周期中发生的操作,不直接与组件渲染相关,但可以对组件的状态、DOM或外部资源产生影响。

useEffect接收两个参数:

  • 回调函数: 定义需要执行的副作用逻辑。
  • 依赖数组: 用于指定在依赖项发生变化时,才重新运行副作用函数。
    如果依赖数组为空,useEffect作用等同于类组件的componentDidMount函数

useEffect还可以提供组件卸载等事项的副作用,这个我们下面会详细介绍。

  1. 如何在DOM渲染后完成后执行?
    示例代码
// 依赖参数为空
useEffect(()=>{
	// 执行网络请求
	// TODO 
	
	// 执行DOM操作
	// TODO
})
  1. 如何对对依赖项进行监听?
    示例代码
export default function TestUseEffect(props ){
    const [text, setText] = useState(""); 
    
    useEffect(()=>{
    	// DOM渲染完成后、依赖项发生变化时执行
        console.log('组件渲染 text:', text); 
    }, [text]) 
    const handleChange = (e)=>{
        setText(e.target.value)
    }
    return (
        <div>
            <input type="text" value={text} onChange={handleChange}/>
            {text}
        </div>
    );
}
  1. 如何在组件将要卸载前执行?
useEffect(()=>{
	// DOM渲染完成后执行
	// TODO
	return ()=>{
		// 组件将要卸载时执行
		// TODO 
	}
})

注意:如果出现useEffect执行了两次的情况,请注释掉React.strictMode标签即可。

useMemo

useMemo的基本用法:

const memoizedValue = useMemo(()=>computeExpensiveValue(a, b), [a,b]);
// computeExpensiveValue是根据a、b的当前值进行计算的函数。当a或b发生改变时,computeExpensiveValue才会被重新执行。
  1. 有依赖项

与vue的computed相同,用于缓存计算过的值,当依赖项数组中的值发生变化,useMemo会重新计算并缓存结果,但是在语法上面略有不同。

  1. 无依赖项

类似useEffect
区别在于:

  • useEffect是无返回值,而useEffect是返回缓存的值。
  • useMemo会在组件渲染之前执行,会比useEffect先执行。

useCallback

useCallback返回一个记忆化的回调函数,它只在其依赖项改变时才会改变。可以帮助我们避免在每次渲染时都创建新的函数,从而提高性能。
useCallback(fn, deps)就是useMemo(()=>fn, deps)的语法糖
useCallback的基本用法:

const callback = React.useCallback(fn, [dependencies]);

useRef

useRef是一个React Hook,帮助引用一个不需要渲染的值

基本用法

const ref = useRef(initialValue)

返回值
useRef返回一个只有一个属性的对象:

  • current: 初始值为initialValue,之后可以将其设置为其他的值。
使用ref引用一个值

在组件顶层调用useRef声明一个或多个ref
在后续的渲染中,useRef将返回相同的对象。可以改变它的current属性来存储信息,并在之后读取它。

  • 与 useState的区别:
    改变ref不会触发重新渲染,ref是存储一些不影响组件视图输出的信息的完美选择。比如一个interval ID并在以后检索它。
const intervalRef = useRef(null);
function handleStartClick(){
	const intervalId = setInterval(()=>{// TODO}, 1000)
	intervalRef.current = intervalId;
}

function handleStopClick(){
	const intervalId = intervalRef.current;
	clearInterval(intervalId)
}
注意事项

在渲染期间读取或写入ref会破坏预期行为。

function MyComponent(){
	// 🚩 不要在渲染期间写入 ref
	myRef.current = 123;

	//  ✅ 在Effect中读取和写入 ref
	useEffect(()=>{
		myRef.current = 123;
	})
	 function handleClick() {
    // ✅ 可以在事件处理程序中读取和写入 ref
    doSomething(myOtherRef.current);
  }
  
	// 🚩 不要在jsx里面读取 ref
	return  <h1>{myOtherRef.current}</h1>; 
}
通过Ref操作DOM

使用ref操作DOM是非常常见的行为。React内置了对它的支持。

import {useRef} from 'react';

function MyComponent(){
	const inputRef = useRef(null);
	
	return <input ref={inputRef} />
}

当React创建DOM节点并将其渲染时,React会将DOM节点设置到Ref对象的current属性。可以借助ref对象访问<input>的DOM节点

function handleClick(){
	inputRef.current.focus();
}

当节点被移除时,React将把current属性设置为null;

避免重复创建Ref的内容

React 会保存 ref 初始值,并在后续的渲染中忽略它。

// 不合适
function Video(){
	const playerRef = useRef(new VideoPlayer());
}

// 建议
function Video(){
	const playerRef = useRef(null);
	if(playerRef.current === null){
		playerRef.current = new VideoPlayer();
	}
}
问题: 无法获取自定义组件的Ref
// 错误的写法
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
// 警告: Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

// 正确的写法
// 将子组件包装在`forwardRef`里面:
import {forwardRef} from 'react';

const MyInput = forwardRef(({value, onChange}, ref)=>{
	return (
		<input 
			value={value}
			onChange={onChange}
			ref={ref}
	)
})

export default MyInput;

最后,父级组件就可以得到它的 ref。

useContext

context类似vue provide的概念,无需将组件传递到每个组件,跨组件级传递变量,实现数据传递共享。

基本用法

  1. 使用createContext创建一个Context
    首先需要创建一个Context对象。这个通常在组件树的顶层完成。
import React from "react";

const ThemeContext = React.createContext(null);

export default function Page() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar theme="dark" />
    </ThemeContext.Provider>
  )
}

function Menu(){
	return (
		// 中间组件不需要在传递ThemeContext的值
		<Button />
	)
}

// 使用useContext
function Button(){
	const theme = React.useContext(ThemeContext)
	return <button>{{theme}}</button>
}
// 使用Consumer也可以的
// function Button() {
//   return <ThemeContext.Consumer>
//     {
//       theme => <button>{theme}</button>
//     }
//     </ThemeContext.Consumer>
// } 

useReducer

reducer类似于状态管理控制器,通过不同的指令对状态做出不同的操作。

参数
  • reducer:用于更新state的纯函数,参数为state和action,返回值是更新后的state。
  • initialArg:用于初始化state的任意值
  • init?:可选参数,用于计算初始值的函数,如果存在则调用init(initialArg), 返回结果作为初始值。
返回值

useReducer返回由两个值组成的数组

  1. state: state初始化的值
  2. dispatch函数,用于更新state并触发组件渲染

    dispatch函数只有一个参数action,代码用户执行的操作,并没有返回值。

基本用法
import { useReducer } from 'react';

function reducer(state, action) {
  swtich(action){
	case "add":
		return state.age + 1;
	case "sub":
		return state.age - 1;
	default: 
		return state;
	}
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  const handleAge = function(){
		dispatch("add");
	}
  return (
  	<div onClick={handleAge} >修改年龄:{state.age}</div>
  )

欢迎关注,免费讨论并解答相关问题

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值