为什么需要(或有用)React Context.Provider?

16

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

因此... 实现需要创建一个组件,称为Context.Provider组件,然后您必须将需要访问共享数据的组件包装在Context.Provider内部。但为什么?为什么要求这样做呢?Context旨在共享数据,使得不属于同一层级的组件能够进行通信,但却需要将组件置于层次结构中才能实现这一目的?

如果能够简单地放弃使用Context.Provider的要求,则使用useContext函数默认提供对一组变量的访问将更加直接且同样有效:

// 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方法所必须要做的...


3
好的,谁在乎呢。这可能是旧的规格。“糟糕的设计”可能是你问题的答案吗? - Thanh Trung
1
我无法回答你的问题,我希望React热潮能够过去,就像比特币热潮一样。 - Sang Dang
@ThanhTrung 是的,哈哈! - ICW
有的。下面的答案展示了使用情况。 - Jonas Wilms
1
  1. 多个提供者。
  2. 范围。我不能再概括了。
- Dennis Vash
显示剩余4条评论
3个回答

2
伙计,答案很简单。React组件只有在其props或state更改时才会重新渲染。没有Context.Provider组件,React将永远不知道何时重新渲染子组件,因此您将拥有过期的、被渲染阻塞的组件。

0
在编程中,Context Provider 的作用是包裹子组件以跟踪状态和属性,并了解父子组件之间的状态和属性如何相互影响。如果 Context Provider 无法跟踪其子组件,那么使用 Context 的组件如何更新呢?(更改父状态会影响子组件,因此可能需要重新渲染)。
同时,理解 React 的哲学和其专注于组件也非常重要,毕竟它是一个基于组件的库。
需要记住的重要事情是:父状态的更改将影响子组件,因此如果父组件的状态发生变化,则子组件将被重新评估,并根据您的组件、状态和数据如何优化(memo、callback 等)进行重新渲染,从而更新这些子组件。

-1

Contexts 适用于所有用例

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

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

来自外部源(Post、Get)的共享数据

Contexts 可以允许我们将与共享状态初始化相关的任何代码存储在上下文本身中,从而使代码更易于阅读和维护。例如,假设我们在服务器上有一些用户文本帖子,这些帖子由我们应用程序中的多个组件显示,并且我们还希望我们的用户能够添加新帖子。所有这些都可以在 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 属性并没有直接传递状态设置函数。相反,我们传递了 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数据,并且当任何一个组件添加新帖子时,另一个组件将使用新数据进行更新。

最终想法

这只是 scratching the surface 的 Context.Provider 实用性。很容易想象使用 Context.Provider 来处理持久性数据存储,方法与上述非常相似,取代 fetch 调用的是存储 / 获取持久性数据的函数。甚至还可以有持久性数据和已获取数据的某种组合。

重新审视我的最初的问题,它确实让我笑了。我有点对,或许应该有一种方法来处理不需要在提供程序中包装组件并且根本不需要任何提供程序代码的简单共享状态的方式。但是,提供程序在应用程序内任何类型的状态管理中都是如此有用,以至于强制人们在简单共享状态时使用它们实际上可能是一件好事,因为那么他们就必须学习这个神奇的工具。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接