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 (
    

{state.text}

); };

这个例子的目的是从输入框中获取文本,并让用户点击按钮以全大写或全小写的形式显示文本。代码使用useReducer声明一个新的reducer。useReducer接受reducer函数和初始状态作为参数,并返回一个数组,我们将其解构为statedispatch变量。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 (
    
    {items.map((item) => (
  • {item} - {counter}
  • ))}
); };

我们使用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 (
    

Count: {count}

Expensive Value: {memoizedValue}

); };

在这个例子中,我们有一个计算成本很高的函数: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 (
    
{animalType === 'dog' ? 'woof' : 'meow'}
); }; ​ const Happy = () => { const { animalType } = useContext(AnimalContext); ​ return (
{animalType === 'dog' ? 'wag tail' : 'purr'}
); }; ​ const App = () => { const [animalType, setAnimalType] = useState('dog'); ​ const toggleAnimalType = () => { setAnimalType(prevAnimalType => (prevAnimalType === 'dog' ? 'cat' : 'dog')); }; ​ return (
{animalType}
); };

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 (
    
); }; ​ const root = ReactDOM.createRoot(document.getElementById("root")); root.render( );

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 (
    

Star Wars Character Details

{data ? (

{data.name}

Height: {data.height}

Mass: {data.mass}

Hair Color: {data.hair_color}

) : (

Loading...

)}
); };

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 (
    
Measure Width

Width: {width}

); };

useImperativeHandle

有时候,你需要直接获取到一个组件的引用。你可以使用useRef来实现这个目的,如果你还想提供对组件的DOM的访问,可以使用useRef结合useImperativeHandle。然而,有时候你需要自定义通过引用暴露给外部的组件行为。为了实现这个目的,你需要使用useImperativeHandle这个hook。对于这个问题,示例胜过千言万语。在示例中,父组件Parent包含一个子组件ExpounderExpounder使用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 (
    
Measure Width

Width: {width}

); };

因此,useImperativeHandle允许你接收第一个参数(原始引用),然后使用第二个参数函数返回的任何对象进行装饰。在这个示例中,我们使用解构赋值创建了一个匿名内联对象,只有一个expound方法:useImperativeHandle(ref, () => ({ expound }))。使用这个hook的结果是,``接收到的引用也有一个expound方法,它可以调用该方法来执行必要的功能。结论使用React提供的更广泛的hooks调色板是充分发挥框架的全部能力的重要方面。在这里,你已经看到了一些有用的示例,而React还提供了更多的hooks。除此之外,还有第三方hooks可用于各种目的和与框架的集成。最后,如果有需要,你还可以定义自己的自定义hooks。

作者:Matthew Tyson

更多技术干货请关注公号“云原生数据库

squids.cn,目前可体验全网zui低价云数据库RDS,免费的数据库迁移工具DBMotion、备份工具、SQL开发工具等

你可能感兴趣的:(react.js,javascript,前端)