8个你需要了解的React hooks
React在JavaScript UI框架中仍然是领先者。在React中有许多正在进行的开发,但过去几年最重要的变化是转向了函数组件。函数组件依赖于hooks来实现许多功能。最常见的hook是useState,但还有许多其他hooks。下面是八个有用的React hooks,你可能还不知道它们,以及如何使用它们。
useReducer
每个人都知道useState,因为它替代了基于类的组件中用于保存状态的成员变量,提供了一个功能等效的函数式替代方案。useState做了类似的事情,但适用于更复杂的场景,其中状态转换更为复杂,并且应用程序从明确的状态转换中受益。这个hook受到了Redux中的reducer的启发。它可以被看作是在简单性和像Redux这样的状态管理系统的复杂性之间的一种折中方案。以下是一个使用useReducer的示例。你也可以在这个JSFiddle中实时查看reducer的使用。
简单的useReducer示例
const initialState = {
text: "",
isUpperCase: false,
};
const reducer = (state, action) => {
switch (action.type) {
case 'SET_TEXT':
return {
...state,
text: action.text,
};
case 'TOGGLE_UPPER_CASE':
return {
...state,
text: state.text.toUpperCase(),
};
case 'LOWERCASE':
return {
...state,
text: state.text.toLowerCase(),
};
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const handleChange = (event) => {
dispatch({ type: 'SET_TEXT', text: event.target.value });
};
const handleToggleUpperCase = () => {
dispatch({ type: 'TOGGLE_UPPER_CASE' });
};
const handleLowerCase = () => {
dispatch({ type: 'LOWERCASE' });
};
return (
<div>
<input type="text" value={state.text} onChange={handleChange} />
<button onClick={handleToggleUpperCase}>Toggle Upper Case</button>
<button onClick={handleLowerCase}>Toggle Lower Case</button>
<p>{state.text}</p>
</div>
);
};
这个例子的目的是从输入框中获取文本,并让用户点击按钮以全大写或全小写的形式显示文本。代码使用useReducer
声明一个新的reducer。useReducer
接受reducer函数和初始状态作为参数,并返回一个数组,我们将其解构为state
和dispatch
变量。const [state, dispatch] = useReducer(reducer, initialState);
reducer函数本身被定义为一个带有两个参数的函数。每当代码中调用dispatch
函数时,它将传递当前状态和一个action对象。在这个例子中,action对象有一个type字段,我们使用它来确定如何改变状态。const reducer = (state, action) =>
在一个中等复杂的应用程序中,useReducer
可以帮助管理复杂性,并且甚至可以使用上下文在整个应用程序中共享。当应用程序因为复杂性而难以管理时,这个hook可以提供帮助。
useCallback
这个hook是一个性能优化的hook。它接受一个函数,并确保只返回一个版本,并在所有调用者之间重复使用。如果这个函数是昂贵的,并且在循环或子组件中被重复调用,这个hook可以带来显著的性能提升。这种优化被称为函数的记忆化。在第二个示例中,我们使用useCallback
来在列表中的多个项目中使用相同的函数。以下是一个在JSFiddle上的实时示例:
在迭代列表中使用useCallback
const List = ({ items }) => {
const [counter, setCounter] = React.useState(0);
const incrementCounter = React.useCallback(() => {
setCounter(counter + 1);
}, [counter]);
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item} - {counter}
<button onClick={incrementCounter}>Increment</button>
</li>
))}
</ul>
);
};
我们使用useMemo
在指定的依赖变量发生变化时创建一个新的记忆化函数。我们可以将这个记忆化函数作为一个普通函数在处理程序中使用。useMemo
接受一个函数作为第一个参数,在这个函数中我们可以执行任何需要的操作。关键的区别是,除非依赖变量列表中的某些内容发生了变化(在我们的示例中是计数器变量),React只会返回函数的缓存值。这在需要在多个调用者之间共享一个昂贵的函数(特别是子组件)的情况下非常有用。请记住,在我们接下来看的useCallback
中,它会将函数本身存储起来。也就是说,它阻止实际函数在每次出现时被重新创建,只在必要时重新创建。
useMemo
useMemo
是缓存函数本身,而useCallback
是缓存函数的返回值。这是一个微妙但重要的区别。什么时候应该使用useMemo
而不是useCallback
?答案是:尽可能使用useMemo
,只有在必要时才使用useCallback
。当你需要避免在渲染过程中创建函数本身时,useMemo
是合适的选择,而useCallback
无法阻止函数在每次出现时被重新创建。然而,useMemo
会确保如果依赖项没有发生变化,函数会返回缓存的值。 在示例中,第三个代码清单展示了useMemo
的使用。
useMemo
const { useMemo, useState } = React;
const ExpensiveComputationComponent = () => {
const [count, setCount] = useState(0);
const computeExpensiveValue = (count) => {
let result = 0;
for (let i = 0; i < count * 10000000; i++) {
result += i;
}
return result;
};
const memoizedValue = useMemo(() => computeExpensiveValue(count), [count]);
return (
<div>
<p>Count: {count}</p>
<p>Expensive Value: {memoizedValue}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
</div>
);
};
在这个例子中,我们有一个计算成本很高的函数:computeExpensiveValue
。它依赖于一个输入变量count
。我们可以使用useMemo
告诉React:只有在count
发生变化时才运行这个函数;否则,返回缓存的计算结果。再次强调,与useCallback
的区别并不明显。重要的区别是:当函数本身被重复实例化并导致性能损耗时,使用useMemo
;否则,使用useCallback
是更好的选择。
useContext
在React中,上下文(Context)是一个存在于组件之外的变量作用域,所有组件都可以访问它。因此,它是一个快速且简单的应用程序范围的全局空间,用于存储应用程序级别的数据。对于复杂的场景,可能更好地使用官方的数据存储库,如Redux,但对于许多用途来说,上下文是足够的。useContext
是函数组件与上下文进行交互的方式。在示例中,我们有两个组件,它们被应用程序的父组件使用。用户可以在之间切换状态,通过全局上下文,子组件将反映出选择的状态。你也可以在JSFiddle上查看这个示例:
操作中的useContext
const { createContext, useContext, useState } = React;
const AnimalContext = createContext();
const Speak = () => {
const { animalType } = useContext(AnimalContext);
return (
<div>
{animalType === 'dog' ? 'woof' : 'meow'}
</div>
);
};
const Happy = () => {
const { animalType } = useContext(AnimalContext);
return (
<div>
{animalType === 'dog' ? 'wag tail' : 'purr'}
</div>
);
};
const App = () => {
const [animalType, setAnimalType] = useState('dog');
const toggleAnimalType = () => {
setAnimalType(prevAnimalType => (prevAnimalType === 'dog' ? 'cat' : 'dog'));
};
return (
<div>
<button onClick={toggleAnimalType}>Toggle animal type</button>
<div>{animalType}</div>
<AnimalContext.Provider value={{ animalType }}>
<Speak />
<Happy />
</AnimalContext.Provider>
</div>
);
};
useRef
useRef
让你在渲染周期之外管理一个引用。当useRef
的值发生变化时,不会导致React引擎重新渲染,而useState
会触发重新渲染。useRef
就像是React旁边的一个特殊区域,它告诉React:“这个变量是特殊的,它不是响应式UI的一部分。”最常见的用例是直接访问DOM及其API。通常,在响应式思维中,应该避免直接操作DOM,而应该通过响应式引擎来完成所有操作,但有时是不可避免的。在示例中,当点击按钮时,我们使用useRef
来保存对输入字段的引用,并使用DOM方法将焦点放在输入字段上,并将其值设置为“Something amazing”。
简单的useRef示例
const App = () => {
const inputRef = React.useRef(null);
const handleClick = () => {
inputRef.current.focus();
inputRef.current.value="Something amazing";
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus Input</button>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<App/>
);
useEffect
useEffect
是第二常用的hook,紧随useState
之后。它经常用于在组件状态发生变化时进行API调用、改变DOM或执行其他操作(即产生效果)。在某种意义上,useEffect
允许你定义一个或多个响应式变量以及它们的行为。 在示例中,我们定义了一个下拉列表来选择一个星球大战角色。当这个值发生变化时,我们向星球大战API(SWAPI)发起API调用,并显示角色的数据。
使用API调用的useEffect
const { useState, useEffect } = React;
const StarWarsCharacter = () => {
const [character, setCharacter] = useState('Luke Skywalker');
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(`https://swapi.dev/api/people/?search=${character}`);
const jsonData = await response.json();
setData(jsonData.results[0]);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, [character]);
const handleCharacterChange = (event) => {
setCharacter(event.target.value);
};
return (
<div>
<h1>Star Wars Character Details</h1>
<select value={character} onChange={handleCharacterChange}>
<option value="Luke Skywalker">Luke Skywalker</option>
<option value="Han Solo">Han Solo</option>
<option value="Princess Leia">Princess Leia</option>
</select>
{data ? (
<div>
<h2>{data.name}</h2>
<p>Height: {data.height}</p>
<p>Mass: {data.mass}</p>
<p>Hair Color: {data.hair_color}</p>
</div>
) : (
<p>Loading...</p>
)}
</div>
);
};
useLayoutEffect
useLayoutEffect
是一个较少被人知晓的hook,在需要对渲染后的DOM进行测量时发挥作用。这个hook在React绘制UI后被调用,因此你可以确保它能够访问到实际的布局。在示例中,我们使用useLayoutEffect
来获取DOM中的一个元素,并在渲染完成后得到通知。然后,我们使用DOM API计算元素的大小。
使用elayouteeffect来度量一个元素
const { useState, useRef, useLayoutEffect } = React;
const App = () => {
const [width, setWidth] = useState(null);
const elementRef = useRef(null);
useLayoutEffect(() => {
if (elementRef.current) {
const { width } = elementRef.current.getBoundingClientRect();
setWidth(width);
}
}, []);
return (
<div>
<div ref={elementRef}>Measure Width</div>
<p>Width: {width}</p>
</div>
);
};
useImperativeHandle
有时候,你需要直接获取到一个组件的引用。你可以使用useRef
来实现这个目的,如果你还想提供对组件的DOM的访问,可以使用useRef
结合useImperativeHandle
。然而,有时候你需要自定义通过引用暴露给外部的组件行为。为了实现这个目的,你需要使用useImperativeHandle
这个hook。对于这个问题,示例胜过千言万语。在示例中,父组件Parent
包含一个子组件Expounder
。Expounder
使用useImperativeHandle
暴露了一个自定义的引用API,使得Parent
可以调用引用,从而触发自定义的行为。请注意,示例中使用了useRef
来包含DOM引用。
useImperativeHandle
const { useState, useRef, useLayoutEffect } = React;
const App = () => {
const [width, setWidth] = useState(null);
const elementRef = useRef(null);
useLayoutEffect(() => {
if (elementRef.current) {
const { width } = elementRef.current.getBoundingClientRect();
setWidth(width);
}
}, []);
return (
<div>
<div ref={elementRef}>Measure Width</div>
<p>Width: {width}</p>
</div>
);
};
因此,useImperativeHandle
允许你接收第一个参数(原始引用),然后使用第二个参数函数返回的任何对象进行装饰。在这个示例中,我们使用解构赋值创建了一个匿名内联对象,只有一个expound
方法:useImperativeHandle(ref, () => ({ expound }))
。使用这个hook的结果是,``接收到的引用也有一个expound
方法,它可以调用该方法来执行必要的功能。结论使用React提供的更广泛的hooks调色板是充分发挥框架的全部能力的重要方面。在这里,你已经看到了一些有用的示例,而React还提供了更多的hooks。除此之外,还有第三方hooks可用于各种目的和与框架的集成。最后,如果有需要,你还可以定义自己的自定义hooks。
作者:Matthew Tyson
更多技术干货请关注公号“云原生数据库”
squids.cn,目前可体验全网zui低价云数据库RDS,免费的数据库迁移工具DBMotion、备份工具、SQL开发工具等。
转载自:https://juejin.cn/post/7257547252540833829