Hooks 是可重用的函数。 它们允许您使用**状态**和其他功能(例如生命周期方法等),而无需编写类。 Hook 函数让我们能够使用函数组件“钩入”React 状态生命周期,从而允许我们操作函数组件的状态,而无需将其转换为类组件。
React 在 16.8 版本中引入了 Hooks,并且此后一直在不断添加更多 Hooks。 其中一些比其他的使用更广泛、更流行,例如useEffect
、useState
和useContext
Hooks。 如果你使用 React,我相信你一定用过这些 Hooks。
但我想关注的是鲜为人知的 React Hooks。 虽然所有 React Hooks 都很有趣,但我真正想向你展示其中五个,因为它们可能不会出现在你的日常工作中——或者也许会,了解它们会给你一些额外的超能力。
useReducer
useReducer
Hook 就像其他 Hooks 一样,是一个状态管理工具。 更具体地说,它是useState
Hook 的替代方案。
如果您使用useReducer
Hook 更改两个或多个状态(或操作),则无需分别操作这些状态。 该 Hook 会跟踪所有状态并集体管理它们。 换句话说:它管理和重新渲染状态更改。 与useState
Hook 不同,useReducer
在处理复杂项目中的多个状态时更容易。
使用案例
useReducer
可以帮助降低处理多个状态的复杂性。 当您发现自己需要集体跟踪多个状态时,请使用它,因为它允许您将组件的状态管理和渲染逻辑视为独立的关注点。
语法
useReducer
接受三个参数,其中一个参数是可选的
- 一个 reducer 函数
initialState(初始状态)
- 一个
init
函数(可选)
const [state, dispatch] = useReducer(reducer, initialState)
const [state, dispatch] = useReducer(reducer, initialState initFunction) // in the case where you initialize with the optional 3rd argument
示例
下面的示例是一个包含文本输入、计数器和按钮的界面。 与每个元素交互都会更新状态。 请注意,useReducer
允许我们一次定义多个 case,而不是单独设置它们。
import { useReducer } from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'USER_INPUT':
return { ...state, userInput: action.payload };
case 'TOGGLE_COLOR':
return { ...state, color: !state.color };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, { count: 0, userInput: '', color: false })
return (
<main className="App, App-header" style={{ color: state.color ? '#000' : '#FF07FF'}}>
<input style={{margin: '2rem'}}
type="text"
value={state.userInput}
onChange={(e) => dispatch({ type: 'USER_INPUT', payload: e.target.value })}
/>
<br /><br />
<p style={{margin: '2rem'}} >{state.count}</p>
<section style={{margin: '2rem'}}>
<button onClick={(() => dispatch({ type: 'DECREMENT' }))}>-</button>
<button onClick={(() => dispatch({ type: 'INCREMENT' }))}>+</button>
<button onClick={(() => dispatch({ type: 'TOGGLE_COLOR' }))}>Color</button>
</section>
<br /><br />
<p style={{margin: '2rem'}}>{state.userInput}</p>
</main>
);
}
export default App;
从上面的代码中,注意到我们能够轻松地在**reducer**(switch-case)中管理多个状态,这展示了useReducer
的优势。 这是它在处理具有多个状态的复杂应用程序时提供的强大功能。
useRef
useRef
Hook 用于在元素上创建 refs 以访问 DOM。 但不仅如此,它还返回一个带有.current
属性的对象,该属性可以在组件的整个生命周期中使用,允许数据持久化而不会导致重新渲染。 因此,useRef
值在渲染之间保持不变;更新引用不会触发重新渲染。
使用案例
当您想要以下操作时,可以使用useRef
Hook:
- 使用存储的可变信息操作 DOM。
- 访问子组件(嵌套元素)中的信息。
- 将焦点设置到某个元素上。
它在存储应用程序中的可变数据而不导致重新渲染时最为有用。
语法
useRef
仅接受一个参数,即**初始值**。
const newRefComponent = useRef(initialValue);
示例
在这里,我使用了useRef
和useState
Hook 来显示在文本输入中键入时应用程序重新渲染更新状态的次数。
import './App.css'
function App() {
const [anyInput, setAnyInput] = useState(" ");
const showRender = useRef(0);
const randomInput = useRef();
const toggleChange = (e) => {
setAnyInput (e.target.value);
showRender.current++;
}
const focusRandomInput = () => {
randomInput.current.focus();
}
return (
<div className="App">
<input className="TextBox"
ref ={randomInput} type="text" value={anyInput} onChange={toggleChange}
/>
<h3>Amount Of Renders: {showRender.current}</h3>
<button onClick={focusRandomInput}>Click To Focus On Input </button>
</div>
);
}
export default App;
请注意,在文本字段中键入每个字符都会更新应用程序的状态,但永远不会触发完全重新渲染。
useImperativeHandle
您知道子组件如何访问从父组件传递给它们的函数吗? 父组件通过 props 将它们传递下来,但这种传递在某种意义上是“单向的”,因为父组件无法调用子组件中的函数。
好吧,useImperativeHandle
使父组件能够访问子组件的函数成为可能。
它是如何工作的?
- 在子组件中定义一个函数。
- 在父组件中添加一个
ref
。 - 我们使用
forwardRef
,允许将定义的ref
传递给子组件。 useImperativeHandle
通过ref
公开子组件的函数。
使用案例
当您希望父组件受到子组件更改的影响时,useImperativeHandle
效果很好。 因此,诸如更改焦点、递增和递减以及元素模糊等情况可能是您使用此 Hook 的情况,以便父组件可以相应地更新。
语法
useImperativeHandle (ref, createHandle, [dependencies])
示例
在此示例中,我们有两个按钮,一个在父组件中,一个在子组件中。 单击父组件按钮将从子组件检索数据,允许我们操作父组件。 它被设置为单击子组件按钮不会将任何内容从父组件传递到子组件,以帮助说明我们如何以相反的方向传递内容。
// Parent component
import React, { useRef } from "react";
import ChildComponent from "./childComponent";
import './App.css';
function useImperativeHandle() {
const controlRef = useRef(null);
return (
onClick={
() => {
controlRef.current.controlPrint();
}
}
>
Parent Box
);
}
export default useImperativeHandle;
// Child component
import React, { forwardRef, useImperativeHandle, useState } from "react";
const ChildComponent = forwardRef((props, ref) => {
const [print, setPrint] = useState(false);
useImperativeHandle(ref, () => ({
controlPrint()
{ setPrint(!print); },
})
);
return (
<>
Child Box
{ print && I am from the child component }
);
});
export default ChildComponent;
输出
useMemo
useMemo
是最不常用但最有趣的 React Hooks 之一。 它可以提高性能并降低延迟,尤其是在应用程序中进行大量计算时。 如何做到这一点? 每次组件的状态更新和组件重新渲染时,useMemo
Hook 都会阻止 React 重新计算值。
您会看到,函数会响应状态更改。 useMemo
Hook 接收一个函数并**返回该函数的返回值**。 它缓存该值以防止花费额外的精力重新渲染它,然后在其中一个依赖项发生更改时返回它。
此过程称为记忆化,它有助于通过记住先前请求的值来提高性能,以便可以再次使用它而无需重复所有这些计算。
使用案例
最佳用例将是您在进行大量计算时想要存储该值并在后续状态更改中使用它。 它可以带来不错的性能提升,但过度使用它可能会产生完全相反的效果,占用应用程序的内存。
语法
useMemo( () =>
{ // Code goes here },
[]
)
示例
单击按钮时,此小型程序会指示数字是偶数还是奇数,然后对该值求平方。 我在循环中添加了许多零以提高其计算能力。 它以毫秒为单位返回该值,并且由于useMemo
Hook 而仍然运行良好。
// UseMemo.js
import React, { useState, useMemo } from 'react'
function Memo() {
const [memoOne, setMemoOne] = useState(0);
const incrementMemoOne = () => { setMemoOne(memoOne + 1) }
const isEven = useMemo(() => {
let i = 0 while (i < 2000000000) i++ return memoOne % 2 === 0
},
[memoOne]);
const square = useMemo(()=> {
console.log("squared the number"); for(var i=0; i < 200000000; i++);
return memoOne * memoOne;
},
[memoOne]);
return (
Memo One -
{ memoOne }
{ isEven ? 'Even' : 'Odd' } { square }
);
}
export default Memo
输出
useMemo
有点像useCallback
Hook,但区别在于useMemo
可以存储来自函数的记忆化值,而useCallback
存储记忆化的函数本身。
useCallback
useCallback
钩子是另一个有趣的钩子,上一节有点剧透了它的作用。
正如我们刚刚看到的,useCallback
的工作方式类似于 useMemo
钩子,它们都使用记忆化来缓存某些内容以供以后使用。useMemo
将函数的计算结果存储为缓存值,而 useCallback
则存储并返回一个函数。
使用案例
与 useMemo
一样,useCallback
是一种不错的性能优化,因为它存储并返回一个记忆化的回调及其任何依赖项,而无需重新渲染。
语法
const getMemoizedCallback = useCallback (
() => { doSomething () }, []
);
示例
{ useCallback, useState } from "react";
import CallbackChild from "./UseCallback-Child";
import "./App.css"
export default function App() {
const [toggle, setToggle] = useState(false);
const [data, setData] = useState("I am a data that would not change at every render, thanks to the useCallback");
const returnFunction = useCallback(
(name) =>
{ return data + name; }, [data]
);
return (
onClick={() => {
setToggle(!toggle);
}}
>
{" "}
// Click To Toggle
{ toggle && h1. Toggling me no longer affects any function }
);
}
// The Child component
import React, { useEffect } from "react";
function CallbackChild(
{ returnFunction }
) {
useEffect(() =>
{ console.log("FUNCTION WAS CALLED"); },
[returnFunction]);
return { returnFunction(" Hook!") };
}
export default CallbackChild;
输出
最终想法
就是这样!我们刚刚介绍了五个非常方便的 React 钩子,我认为它们经常被忽视。与许多类似的总结一样,我们只是触及了这些钩子的表面。它们各自都有自己的细微差别和需要在使用时考虑的事项。但希望您对它们是什么以及何时比您可能更常使用的其他钩子更适合有一个很好的高级概念。
完全理解它们的最佳方法是通过实践。因此,我鼓励您在您的应用程序中练习使用这些钩子以更好地理解它们。为此,您可以通过查看以下资源了解更多详细信息
- React 钩子入门 (Kingsley Silas)
- 钩子概览 (React 文档)
- 钩子速查表 (Ohans Emmanuel)
- React 生命周期循环 (Kingsley Silas)
- React Router 的钩子 (Agney Menon)
- 使用 Enzyme 和 React Testing Library 测试 React 钩子 (Kingsley Silas)
感谢您撰写这些内容!有一些新的官方 React 文档在 beta.reactjs.org 上以 React 钩子优先的方式进行教学。请查看它们!
谢谢 Rachel。会的!
React 文档指出,useMemo 仅用于优化,并不保证永远不会重新计算。
当您需要它不重新计算时(例如,如果它使用随机值),您将如何实现它?您会使用
useRef
吗?或者可能是useState
的初始值(即使它不是状态)?我找不到自然的方法。在 useRef 示例中,您所说的完整重新渲染是什么意思?在每个输入中,示例中的重新渲染次数都在增加,那么它有什么不同?
感谢分享!我认为我需要重构工作代码库中的一个文件以使用 useReducer 钩子,因为现在的状态太多了
一个问题,在 useImperativeHandle 钩子中,我注意到传递给子组件的
ref
不会作为属性添加到任何子组件元素中。因此,使用
forwardRef
函数,是否意味着每个子组件元素都通过父组件上的 ref 钩子进行引用,或者只是添加到useImperativeHandle
钩子的函数controlPrint()
正在被引用?感谢分享文章。
虽然我不确定 useMemo 代码部分示例是否按预期实现,以证明 useMemo 回调 (x2) 不应重新计算,而应返回缓存值。
您会看到,由于这两种情况下的 useMemo 都依赖于您当前代码示例中“count”变量的变化,“count”变量在每次单击“Memo One”按钮时(通过“setCount()”)都会更新,因此 useMemo 函数中的回调在每次单击时都会重新计算。
这是否会违背试图证明 useMemo 应该返回缓存值而不是在每次点击时连续重新计算的目的?
或者我误解了什么?
我注意到我的原始回复中有一个打字错误。因此,而不是
“count”和“setCount()”,我的意思是“memoOne”和“setMemoOne()”
管理员或用户能否告诉我 WordPress 中 CSS tricks 的显示代码是如何工作的,我的意思是它是否使用任何插件或直接使用任何 PHP 代码?请
Chris 在这里有一篇关于它的帖子:https://css-tricks.org.cn/posting-code-blocks-wordpress-site/