React Context API详解

109 阅读3分钟

1. prop-drilling

假设在如下的组件树中,B组件和F组件都需要使用count这个React state,传统的解决方案是,在App组件中定义count,然后将count作为组件的props一级一级往下传,直至传到B组件和F组件。

image.png

以B组件为例,传递count的代码应该是这样:

import { useState } from "react";

export default function App() {
    const [count, setCount] = useState(0);   
    return (
        <div className="App">
            <h1>This is App component</h1>
            <A count={count} />
        </div>
        );
}

const A = ({ count }) => {
    return <B count={count} />;
};
  
const B = ({ count }) => {
    return <p>{count}</p>;
};

可以看出,组件A其实并不需要count,它接受count作为props的作用仅仅是用于将它传递给子组件B。当组件树层级过多时,不必要的层层传递会造成prop-drilling问题,使代码繁杂、可读性下降。

2. Context API

React提供了Context API解决了上述问题,允许组件树上的任何一个组件读取state。

image.png

Context API是一个在整个应用程序内部传递数据的系统,避免了沿着组件树手动传递props的问题。可以说它允许我们向整个应用程序“广播”全局状态。

Context API有三个组成部分,分别是:

  1. Provider:一个特殊的React组件,允许所有子组件访问,一般放在根组件App中
  2. value:提供其他组件访问的数据,通常是state和function
  3. Consumer:所有需要读取Provider提供的value的组件都称为Consumer

一般来说,一个application中往往有一个Provider,多个value和多个Consumer。在上面的例子中,Provider是App组件,value是要传递的值count,Consumer是需要使用count的组件B和F。

3. Context API的re-render功能

当Provider提供的value更新时,注意:所有的Consumer将自动re-render!

也就是说,除了setState之外,value的更新同样会触发组件的重新渲染。

image.png

4. 使用Context API

使用Context API仅需三步:

  1. 使用React提供的createContext方法创建context,此方法返回两个组件Provider和Consumer。
const PostContext = createContext();
  1. 用Provider组件包裹需要传值的组件(往往是根组件)
function App() {
 //需要传递给子组件的数据,包括state和function
  const [posts, setPosts] = useState([]);
  const [searchQuery, setSearchQuery] = useState("");
  function handleAddPost(post) {...}
  function handleClearPosts() {...}

  return (
        //PostContext.Provider组件包裹子组件,并在value中以对象的方式定义要传递的数据
       <PostContext.Provider
          value={{
            posts: searchedPosts,
            onAddPost: handleAddPost,
            onClearPosts: handleClearPosts,
            searchQuery,
            setSearchQuery,
          }}
        >
    
          <section>
            ...
          </section>
    </PostContext.Provider>
  );
}

也就是说,在这个应用程序任何子组件中,都可以访问App组件提供的value:posts、onAddPost、onClearQuery、 searchQuery、setSearchQuery,而不需要使用prop传递造成代码臃肿。

  1. 在需要值的组件内部使用useContext钩子接收值(常用对象解构接收)
function Results() {
  const { posts } = useContext(PostContext);

  return <p>🚀 {posts.length} atomic posts found</p>;
}

5. 自定义组件和钩子

value过多时,可以将step1和step2定义为自定义组件。

useContext(PostContext)也可以定义为自定义钩子。

由于EsLint新规定,禁止从同一模块导出 React 组件和非组件,因此自定义组件和钩子不能放在一个文件夹中定义并导出,但是可以导出Context组件和Provider组件。

//自定义组件PostProvider
import React, { createContext, useContext, useState } from "react";

const PostContext = createContext();

const PostProvider = ({ children }) => {
  const [posts, setPosts] = useState();
  const [searchQuery, setSearchQuery] = useState("");
  const searchedPosts = ...
  function handleAddPost(post) {...}
  function handleClearPosts() {...}
  
  return (
    <PostContext.Provider
      value={{
        posts: searchedPosts,
        onAddPost: handleAddPost,
        onClearPosts: handleClearPosts,
        searchQuery,
        setSearchQuery,
      }}
    >
      {children}
    </PostContext.Provider>
  );
};

export { PostProvider, PostContext };

//自定义钩子usePosts
import PostContext from "./PostContext";

const usePosts = () => {
  const context = useContext(PostContext);
  return context;
};

export default usePosts;