为什么 React Context.Provider 是必要的(或有用的)?

Yun*_*Gun 13 javascript datacontext reactjs react-redux

React 有上下文的原因是允许多个兄弟组件共享一块状态数据。它是允许两个不相关的组件在共享变量中读/写的首选方法。之所以有必要,是因为 React 无法轻松地将数据值提供给多个屏幕,而无需在屏幕之间实际传递该数据。相反,它允许每个屏幕在需要时访问数据。

所以...实现需要创建一个组件,称为Context.Provider组件,然后你必须将需要访问共享数据的组件包装在Context.Provider. 但为什么?为什么这是一个要求?Contexts 旨在在不具有层次结构相关的组件之间共享数据,并且需要将组件置于层次结构中才能这样做?

简单地放弃使用 Context.Provider 的要求将是直接的 100 倍,并且同样有效,简单地让useContext函数默认访问设置变量:

// In ctx.js
import React from 'react';
export default CTX = React.createContext({val: "value"});
Run Code Online (Sandbox Code Playgroud)
// In compA.js
import CTX from './ctx.js';
import {useContext} from 'react';
function A(props) {
    var [context, setContext] = useContext(CTX);
    console.log(context); //Logs {val: 'value'};
    setContext({val: "newValue"});
}
Run Code Online (Sandbox Code Playgroud)

然后,假设组件 B 在 A 之后呈现:

import CTX from './ctx.js';
import {useContext} from 'react';
function B(props) {
    var [context, setContext] = useContext(CTX);
    console.log(context); //Logs {val: 'newValue'};
}
Run Code Online (Sandbox Code Playgroud)

上面的用法,如果真的有效的话,解决了“在不相关的组件之间共享数据”的任务,比要求在上下文文件中定义一个全新的组件要简单得多。此解决方案更好,因为: 1. 不需要重构应用程序。您不需要在提供程序中包装组件。2. 任何组件都可以轻松地请求任何共享状态,并且它们可以轻松设置共享状态。3. 使用更少的代码更容易理解(一行代码用于导入,一行代码用于启动上下文)。4. 不牺牲任何东西。这种方法允许在组件之间轻松共享状态,这首先是上下文的全部原因。

我疯了吗?我们绝对需要将我们的组件包装在一个特殊的组件中以共享数据是否有正当理由?.. 为什么共享状态不能独立存在?就像他们选择了一个糟糕的解决方案......为什么让每个开发人员在使用共享状态之前将组件包装在另一个组件中,为什么不让开发人员在需要使用它时使用该死的共享状态而不是跳过箍?有人请教我。

编辑:一个答案说,使用我描述的方法,我们将无法使用单个组件访问多个上下文。那是假的。使用我描述的方法实际上更容易:

// In context.js
export const CTX = React.createContext({val: "val"});
export const CTX2 = React.createContext({val2: "val2"});
Run Code Online (Sandbox Code Playgroud)
// In app.js

function App(props) {
    const [state, setState] = useContext(CTX);
    const [state2, setState2] = userContext(CTX2);
    return (<></>);
}
Run Code Online (Sandbox Code Playgroud)

简单。不需要Context.Provider。这是在一个组件中使用的多个上下文,只需要两次调用 useContext 而不是将整个应用程序包装在两个嵌套的上下文中,这就是您对当前Context.Provider方法所做的...

Yun*_*Gun -1

上下文是为了处理所有用例而设计的

从那时起,我花了更多时间在应用程序中使用上下文,并逐渐意识到这Context.Provider在各种情况下都非常有用。我最初的抱怨是有道理的,因为很多时候在使用时Context我们只是想要一种可以在组件之间共享的状态变体。在这个常见的用例中,Context.Provider确实需要我们编写一些不必要的样板代码,并且需要我们将元素包装在提供程序中,以便它们可以访问上下文。

然而,每当我们的共享状态变得更加复杂时,有一个专用的Context.Provider组件就可以让我们的生活变得更加轻松。这是一个需要考虑的用例

来自外部来源的共享数据(发布、获取)

上下文可以允许我们在上下文本身中存储与共享状态初始化相关的任何代码,从而产生更容易阅读和维护的代码。例如,假设我们的服务器上有一些用户文本帖子,这些帖子由应用程序中的多个组件显示,并且我们还希望我们的用户能够添加新帖子。所有这些都可以在以下内部非常巧妙地处理Context.Provider

import React, {useContext, useEffect, useState} from 'react';
export const PostsContext = React.createContext([]);

export default PostsContextProvider({children}) {
    const [posts, setPosts] = useState([]);
    
    function fetchPosts() {
        // Here we will fetch the posts from our API, and then set the state
        // stored within the Context.Provider equal to the fetched posts.
        fetch('https://www.fakewebsite.com/api/posts/get', {
            method: 'GET',
            headers: {'Content-Type': 'application/json'}
        }).then((response)=>{
            // Convert response to json
            return response.json();
        }).then((json)=>{
            // json here is the posts we fetched from the server, so we set the state
            // equal to this value. This will update the state within all components
            // that are using the context.
            setPosts(json.posts);
        })
    }

    useEffect(function(){
        // This function will run a single time when the application is started
        fetchPosts();
    },[])

    function addNewPost(post) {
        // This is the function that will be used by the components.
        // First, we will send the new post to the server so that it can store it.
        fetch('https://www.fakewebsite.com/api/posts/post', {
            method: "POST",
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({post: post})
        }).then((response)=>{
            if(response.ok) {
                // The server has updated its database with our new post.
                // Now we just need to fetch the posts from the server again to get the updated data.
                fetchPosts();
            } 
        })
    }
    return (
        <PostsContext.Provider
            value={[posts, addNewPost]}
        >
            {children}
        <PostsContext.Provider />
    )
}
Run Code Online (Sandbox Code Playgroud)

请注意,value我们传递的 prop 实际上并未直接传递状态设置函数。相反,我们传递该addNewPost函数。因此,当组件调用时,useContext(PostsContext)它们将获得该addNewPost函数。这非常有用,它允许我们的组件轻松地将单个帖子添加到共享状态,同时还处理服务器更新!很酷。使用我最初提出的解决方案,这是不可能的,因为我们只能从useContext调用中获得一个简单的状态设置函数。

现在,我们必须将应用程序包装在提供程序中,以使其可供所有组件使用:

// App.js

import React from 'react';
import PostsContextProvider from './posts_context';
import MyComponent from './my_component';
import MyOtherComponent from './my_other_component';

export default function App() {
    return (
        <PostsContextProvider>
            <MyComponent/>
            <MyOtherComponent/>
        </PostsContextProvider>
    )
}

Run Code Online (Sandbox Code Playgroud)

此时,MyComponent现在MyOtherComponent可以使用钩子访问上下文了useContext。现在,组件访问帖子数据并用新帖子更新它变得非常简单。

import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyComponent() {
    const [posts, addPost] = useContext(PostsContext); // 'posts' will always be up to date with the latest data thanks to the context.
    
    ...
}
Run Code Online (Sandbox Code Playgroud)
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyOtherComponent() {
    const [posts, addPost] = useContext(PostsContext); 
    
    ...
    function handleAddPost(title, text) {
        // Now when this component wants to add a new post, 
        // we just use the `addPost` function from the context.
        addPost({title, text});
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处在于,与获取和发布数据相关的所有代码都可以整齐地包含在提供程序中,与 UI 代码分开。每个组件都可以轻松访问数据posts,并且当任一组件添加新帖子时,另一个组件将使用新数据进行更新。

最后的想法

这只是 的用途的冰山一角Context.Provider。很容易想象使用Context.Provider与上面非常相似的方法来处理持久数据存储,fetch用存储/获取持久数据的函数替换调用。或者甚至是持久数据和获取数据的某种组合。

重新审视我最初的问题后,它实际上让我笑了。我是对的,也许应该有一种方法来处理组件之间的简单共享状态,不需要将组件包装在提供程序中,并且根本不需要任何提供程序代码。然而,提供程序在应用程序内的任何类型的状态管理中都非常有用,以至于强制人们将它们用于简单的共享状态实际上可能是一件好事,因为这样他们就必须了解这个奇妙的工具。