likes
comments
collection
share

8个你需要了解的React hooks

作者站长头像
站长
· 阅读数 33

​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函数和初始状态作为参数,并返回一个数组,我们将其解构为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 (
    <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包含一个子组件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 (
    <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
评论
请登录