React-Hooks

React Hooks

Hooks 的目的是加强函数组件,完全不使用"类"组件,就能写出一个全功能的组件, A Hook is a special function that lets you “hook into” React features.根据官网的说法,Hook就是一个特殊的函数,让你可以在函数组件中使用一些 Hooks(钩子)

类组件的缺点

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
  • 组件类引入了复杂的编程模式,比如 render props 和高阶组件。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。

四个常用的钩子

  • useState() 状态钩子
  • useContext() 共享状态钩子
  • useReducer() action 钩子
  • useEffect() 副作用钩子

useState() 状态钩子

用于为函数组件引入状态 state,纯函数不能有状态,因此需要通过钩子来引入

import React, { useState } from "react";

function Example() {
  // Declare a new state variable, which we'll call "count"
  // useState 返回一个数组,通过解构的方式来获取对应的值
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

useState()这个函数接受状态的初始值,作为参数,上例的初始值为count的初始值。该函数返回一个数组,数组的第一个成员是一个变量(上例是 count),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是 set 前缀加上状态的变量名(上例是 setCount)

如果我们想要存储两个不同的值在 state 中,那么我们可以调用两次 useState()


const [count, setCount] = useState(0);
const [isLoading, setLoading] = useState(true);
....

useContext() 共享状态钩子

如果需要在组件之间共享状态,可以使用 useContext()

const value = useContext(myContext);

复制代码 useContext 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。

别忘记 useContext 的参数必须是 context 对象本身:

正确: useContext(MyContext)
错误: useContext(MyContext.Consumer)
错误: useContext(MyContext.Provider)

现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。

<div className="App">
  <Navbar />
  <Messages />
</div>

使用 React Context API 在组件外创建一个 Context

const AppContext = React.createContext({});
// 如果组件是单独的文件,那么需要在这里把AppContext导出
// export const AppContext = React.createContext({})

组件封装代码如下

<AppContext.Provider
  value={{
    username: "superawesome"
  }}
>
  <div className="App">
    <Navbar />
    <Messages />
  </div>
</AppContext.Provider>
// 在需要的地方导入AppContext
import { AppContext } from "../index";

export default function Navbar() {
  // console.log(useContext())
  const { username } = useContext(AppContext);
  return (
    <div className="navbar">
      <p>Navbar</p>
      <p>{username}</p>
    </div>
  );
}

useReducer() action 例子

React 本身不提供状态管理的功能,通常会用到 Redux

Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState。

useReducers()钩子用来引入 Reducer 功能。

const [state, dispatch] = useReducer(reducer, initialState);

上面是 useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的 dispatch 函数。

import React, { useReducer } from "react";
import ReactDOM from "react-dom";
import "./styles.css";

const myReducer = (state, action) => {
  switch (action.type) {
    case "countUp":
      return {
        ...state,
        count: state.count + 1
      };
    default:
      return state;
  }
};

function App() {
  const [state, dispatch] = useReducer(myReducer, { count: 0 });

  return (
    <div className="App">
      <button onClick={() => dispatch({ type: "countUp" })}>+1</button>
      <p>Count: {state.count}</p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

由于 Hooks 可以提供共享状态和 Reducer 函数,所以它在这些方面可以取代 Redux。但是,它没法提供中间件(middleware)和时间旅行(time travel),如果你需要这两个功能,还是要用 Redux。

useEffect() 副作用钩子

useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在 componentDidMount 里面的代码,现在可以放在 useEffect()。

useEffect()的用法如下。

useEffect(() => {
  // Async Action
}, [dependencies]);

上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项(简单理解就是异步请求需要的参数),只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行 useEffect()。

Tip: useEffect 可以理解为 componentDidMount componentDidUpdate componentWillUnMount三个生命钩子函数的结合

const Person = ({ personId }) => {
  const [loading, setLoading] = useState(true);
  const [person, setPerson] = useState({});

  useEffect(() => {
    setLoading(true);
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then(response => response.json())
      .then(data => {
        setPerson(data);
        setLoading(false);
      });
  }, [personId]);

  if (loading === true) {
    return <p>Loading ...</p>;
  }

  return (
    <div>
      <p>You're viewing: {person.name}</p>
      <p>Height: {person.height}</p>
      <p>Mass: {person.mass}</p>
    </div>
  );
};

每当组件参数 personId 发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。

effect 可以分为两类:一类是不需要清除的,比如:网络请求、手动修改 DOM、日志打印,还有一类是需要清除的,比如:监听事件。

不需要清除的对比 Class 组件,其实只是使用了 componentDidMount componentDidUpdate这两个生命钩子,但是使用了 useEffect 后,不用重复些一些代码在这两个函数中

需要清除的对比 Class 组件,使用了 componentDidMount componentDidUpdate componentWillUnMount三个生命钩子函数,需要在卸载钩子函数中,把一些 Effect 进行清除,比如移除监听事件等。

在 Hook 中我们不需要单独的在某个钩子函数中添加清除的操作(并且函数组件中本来就没有生命周期钩子函数),在 useEffect 中我们可以通过返回一个函数的方式来实现清除的操作

当需要清理时,React 将运行它

// 这是官网上的一个例子
useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  // Specify how to clean up after this effect:
  return function cleanup() {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
});

卸载组件时,React 执行清理。但是,正如我们之前所了解的,效果会在每个渲染中运行,而不仅仅是一次。这就是为什么 React 在下一次运行效果之前还要清除先前渲染中的效果的原因。

useEffect 同样可是使用多次,相比于 Class 组件中把同一个逻辑分割到各个生命周期中,在一个 useEffect 中就是一个完整的逻辑,使用多个 useEffect 就是多个逻辑

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

useEffect 的第二个参数,我们在实际的场景中会有这样一个情况,就是在重新渲染的时候,如果某些值没有改变那么我们就让 React 跳过应用某些效果。

class 中

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

useEffect 中,我们可以传递一个数组作为 useEffect 的第二个参数,意思是只有当这个参数的值发生改变的时候才会触发 effect

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

Tips:

  • 如果使用此优化,请确保数组包含组件范围中所有随时间变化并被效果使用的值(例如 props 和 state)。
  • 如果要运行效果并仅将其清除一次(在挂载和卸载时),则可以传递空数组([])作为第二个参数。

Hooks 的使用规则

顶层调用 Hooks

不要在循环中、条件判断或者嵌套的函数中调用 Hooks,因为这样才能保证 hooks 每次的调用顺序是不变的,react 才能准确的找到对应的 hook

React 依赖于 Hook 的调用顺序。

function Form() {
  // 1. Use the name state variable
  const [name, setName] = useState("Mary");

  // 2. Use an effect for persisting the form
  useEffect(function persistForm() {
    localStorage.setItem("formData", name);
  });

  // 3. Use the surname state variable
  const [surname, setSurname] = useState("Poppins");

  // 4. Use an effect for updating the title
  useEffect(function updateTitle() {
    document.title = name + " " + surname;
  });

  // ...
}

react 就会依照这如下的顺序找到对应的 hook

useState("Mary"); // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm); // 2. Add an effect for persisting the form
useState("Poppins"); // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle); // 4. Add an effect for updating the title

如果我们把某个 hook 放在 if 条件中

if (name !== "") {
  useEffect(function persistForm() {
    localStorage.setItem("formData", name);
  });
}

这样就会导致 hook 的顺序发生改变

useState("Mary"); // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm)  // 🔴 This Hook was skipped!
useState("Poppins"); // 🔴 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle); // 🔴 3 (but was 4). Fail to replace the effect

我们不能把某个 hook 放在条件中,但是我们可以在 hook 中添加条件判断等来决定是否执行

useEffect(function persistForm() {
  // 👍 We're not breaking the first rule anymore
  if (name !== "") {
    localStorage.setItem("formData", name);
  }
});

函数组件中调用 Hooks

  • 不要在常规的 js 函数中调用 Hooks
  • 可以在 React 函数组件中调用
  • 可以在自定义的 Hooks 中调用

自定义 Hooks

自定义 Hooks 可以帮助我们解决一些代码逻辑的复用,之前我们了解的复用的方式有 render props 和 HOC ,自定义 Hooks 也可以。

自定义 hooks 也是 js 的一个函数,但是我们约定以use开头(因为 hooks 的规则中,不能再常规的 js 函数中使用 hooks,因此这里必须以 use 开头,告诉 react 这是一个自定义的 hook),每次使用自定义挂钩时,其中的所有状态和效果都是完全隔离的。

Hooks API

参考文档 Hooks API Reference

  • 基础 Hooks

    • useState
    • useEffect
    • useContext
  • 其他 Hooks

    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值