繁体   English   中英

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

[英]Why Is React Context.Provider Necessary (Or useful)?

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

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

简单地放弃使用 Context.Provider 的要求会更直接 100 倍并且同样有效,简单地让useContext函数默认提供对 set 变量的访问:

// In ctx.js
import React from 'react';
export default CTX = React.createContext({val: "value"});
// 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"});
}

然后稍后,假设组件 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'};
}

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

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

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

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

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

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

伙计,答案很简单。 React 组件仅在 props 或 state 更改时重新渲染。 如果没有 Context.Provider 组件,react 将永远无法理解何时重新渲染子组件,因此您将拥有陈旧的、被渲染阻止的组件。

让 Context Provider 环绕孩子的目的是跟踪状态和道具,阅读父母和孩子之间的状态和道具如何相互影响。 如果 Context Provider 无法跟踪其子项,那么使用 Context 的组件将如何更新(更改父状态会影响子项,因此可能需要重新渲染)。

理解 React 的哲学也很重要,它专注于组件,毕竟它是一个基于组件的库。

重要的是要记住:父状态的变化会影响子组件,所以如果父组件的状态发生变化,子组件将被重新评估,并且根据你的组件、状态和数据的优化方式(备忘录、回调等),可能会发生重新渲染,从而也更新这些子组件。

一直在为这个问题而苦苦挣扎,直到我找到这个https://github.com/pmndrs/zustand我为每个商店创建了一个实例,不需要 Provider,我认为这就是我们想要的

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

所以...实现需要创建一个称为Context.Provider组件的组件,然后必须将需要访问共享数据的组件包装在Context.Provider 但为什么? 为什么这是一项要求? Context被设计为在没有层次关系的组件之间共享数据,并且需要将这些组件置于层次结构中吗?

直接降低使用Context.Provider的要求将是100倍,并且同样有效,简单地让useContext函数默认情况下可以访问set变量:

// In ctx.js
import React from 'react';
export default CTX = React.createContext({val: "value"});
// 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"});
}

然后稍后,假设组件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'};
}

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

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

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

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

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

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

上下文是为了处理所有用例

从那以后,我花了更多时间在我的应用程序中使用 Contexts,并逐渐意识到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 />
    )
}

请注意,我们传递的value prop 实际上并没有直接传递 state setter 函数。 相反,我们传递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>
    )
}

此时, MyComponentMyOtherComponent现在可以使用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.
    
    ...
}
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});
    }
    ...
}

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

最后的想法

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

在重新审视我最初的问题时,它实际上让我发笑。 我是对的,也许应该有一种方法来处理组件之间的简单共享状态,不需要将组件包装在提供者中,也根本不需要任何提供者代码。 然而,提供者在应用程序中的任何类型的状态管理中都非常有用,迫使人们将它们用于简单的共享状态实际上可能是一件好事,因为那样他们将不得不了解这个很棒的工具。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM