【问题标题】:React-Redux connect: Use mapStatetoProps to inject only component part of storeReact-Redux 连接:使用 mapStatetoProps 仅注入 store 的组件部分
【发布时间】:2017-04-28 12:10:59
【问题描述】:

[React-Redux] 问题。

我希望在任何应用程序或应用程序商店的任何级别中使用可重用的封装组件。

当涉及到使用“mapStatetoProps”然后制作组件容器(将状态作为道具注入组件中)时,您总是会收到整个商店。如果您想动态地或在其他项目中重用组件,这可能会很痛苦。

问题是,如果您使用相同的商店条目,但您想使用与封装模块相同的组件,它们将共享相同的数据。

而且,当您封装组件并重用它们并且它们深深嵌套在存储中时,您最终需要知道它们在哪里。

一个可能的丑陋解决方案是实现一个脚本,遍历 mapStateToProps 中的状态,直到找到与某个名称匹配的键。这里的问题是确保您要使用的状态字段是唯一的。

我非常乐意以优雅的方式了解此问题的任何适当解决方案。

或者,也许我们在谈论 react-redux-sagas 应用时只是想错了。

【问题讨论】:

    标签: reactjs redux redux-saga


    【解决方案1】:

    为了这个例子,我将讨论一个可重用的编辑器组件,它编辑文档并将它们提交到服务器。

    在使用编辑器的代码中,我给每个编辑器一个唯一的 id。例如

    const Comment = (props) => {
      return <div>
        <h3>Add new comment</h3>
        <Editor editorId={`new-comment-${props.commentId}`} />
      </div>
    }
    

    在 redux 状态下,我有一个 subreducer 编辑器,其对象由 editorId 键入,因此 redux 状态类似于:

    {
      otherStuff: {...},
      editor: {
        "new-comment-123": { isActive: true, text: "Hello there" },
        "new-comment-456": { isActive: false, text: "" },
        ...
      },
      ...
    }
    

    然后在编辑器mapStateToProps 我使用选择器来获取正确实例的数据:

    const mapStateToProps = (state, ownProps) => {
      return {
        isActive: selectors.isActive(state, ownProps.editorId),
        text: selectors.text(state, ownProps.editorId)
      }
    }
    

    选择器以reselect 样式构建,可以手动构建,也可以实际使用重新选择。示例:

    // Manual code
    export const getEditor = (state, editorId) => state.editor[editorId] || {};
    export const isActive = (state, editorId) => getEditor(state, editorId).
    export const text = (state, editorId) => getEditor(state, editorId).text;
    
    // Same in reselect
    import { createSelector } from 'reselect'
    
    export const getEditor = (state, editorId) => state.editor[editorId] || {};
    export const isActive = createSelector([getEditor], (editorData) => editorData.isActive);
    export const text = createSelector([getEditor], (editorData) => editorData.text);
    

    如果你想扩展它以在多个应用程序中使用,你需要导出你的组件、reducer 和 sagas。有关工作示例,请查看 https://github.com/woltapp/redux-autoloader 甚至 http://redux-form.com

    【讨论】:

    • 您能否进一步解释一下您如何处理重新选择?您是否需要在使用组件或类似组件的地方指定根路径作为选择器?
    • 我添加了一个选择器的示例,它们在各种应用程序中都非常有用:您已经在一个地方定义了如何从 redux 中检索某种类型的数据,这使得重构 redux 状态变得更加容易.当然,许多mapStateToProps 函数中都使用了相同的选择器。
    • 这几乎是一个有效的解决方案,但仍然不是我想要的,问题是我正在寻找一种方法,将组件放置在任何级别并将其连接到父状态重新选择,然后父级将连接到其父级等等,所以它就像一个反向级联连接,所以你可以把任何东西放在你想要的任何地方。
    • 我猜你可以这样做,但这意味着你在你的 redux 状态中有深度嵌套的结构。不建议这样做,redux 建议您规范化状态并通过 id 引用嵌套对象,将它们的数据保存在状态的另一部分。我个人发现这是一种很好的做法,它可以简化代码并减少不同步的错误。查看redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
    【解决方案2】:

    如果我正确理解您的担忧,您可以实现mapStateToProps,就好像它接收到您需要的状态部分并调用它,例如mapStateToYourComponentProps,实际上mapStateToProps 您只需调用mapStateToYourComponentProps 并通过它是状态的一部分

    【讨论】:

    • 问题是我不想知道什么是状态的适当部分。这是我们去“自动化”的部分
    【解决方案3】:

    我找到了一种使组件完全独立于应用程序中的状态和层次结构的方法。

    基本上,每个组件都必须公开一个方法来设置状态内的路径。然后,在使用它之前导入它时,您必须对其进行初始化。你也可以用另一种方式实现它,这样你就可以将它作为道具内联接收。

    它利用重新选择来建立选择。

    每个组件都知道它在状态中的键名。

    根组件会导入其他组件,它会调用每个传递根组件路径的组件的setPath方法。

    然后每个组件将调用每个子组件的 setPath 传递他们自己在状态中的位置。 “每个父母都会初始化他们的孩子”

    所以每个组件都会根据命名为“父路径+本地路径(存储中的组件键名)”在存储中设置一个路径。

    这样,您将使用重新选择的“createSelector”方法定义嵌套路由,如下所示:['rootKey','subComponent1Key','subsubComponent1Key]。

    这样,您就完成了存储隔离。 Redux 操作只会更改所需的部分,因此框架也涵盖了这部分。

    它对我来说就像一个魅力,请在我将其标记为好之前告诉我它是否好。

    【讨论】:

    • 请注意,如果您的组件在页面中是唯一的,这将起作用,因为导入是单例的,如果您尝试设置重复组件的路径,它将影响同一个模块。如果您需要在页面中的多个位置拥有相同的容器,那么您将需要找到另一种解决方案,或者一种在工厂中隔离逻辑的方法,使其不再是单例
    【解决方案4】:

    如果你有空闲时间,试试我最近写的 npm 包 redux-livequery (https://www.npmjs.com/package/redux-livequery)。

    还有另一种管理活动列表的方法。

        let selector0 = (state) => state.task.isComplete;
        let selector1 = (state) => state.task.taskList;
        this.unsub2 = rxQueryBasedOnObjectKeys([selector0, selector1], ['isActive', 'task'], (completeTaskList) => {
            // equal SQL =>
            // select * from isActive LEFT JOIN taskList on isActive.child_key == taskList.child_key
            console.log("got latest completeTaskList", completeTaskList);
            // you can do whatever you want here
            // ex: filter, reduce, map
    
            this.setState({ completeTaskList });
        }, 0);
    

    在减速器中:

        case "MARK_ACTIVE_TASK": {
            let { id } = action.meta;
            return update(state, { isActive: { [id]: { $set: { active: Date.now() } } } });
        }
        case "UNMARK_ACTIVE_TASK": {
            let { id } = action.meta;
            return update(state, { isActive: { $apply: function (x) { let y = Object.assign({}, x); delete y[id]; return y; } } });
        }
    

    它让你有更简单的减速器。此外,没有更多的嵌套选择器函数或过滤器,这是非常昂贵的操作。将所有逻辑放在同一个地方会很棒。

    它还可以做更复杂的操作,比如如何获得完整和活跃的列表。

        let selector0 = (state) => state.task.isComplete;
        let selector1 = (state) => state.task.isActive;
        let selector2 = (state) => state.task.taskList;
        this.unsub3 = rxQueryInnerJoin([selector0, selector1, selector2], ['isComplete', 'isActive', 'task'], (completeAndActiveTaskList) => {
            // equal SQL =>
            // select * from isComplete INNER JOIN isActive on isComplete.child_key == isActive.child_key
            //                          INNER JOIN taskList on isActive.child_key == taskList.child_key
            console.log("got latest completeAndActiveTaskList", completeAndActiveTaskList);
            // you can do whatever you want here
            // ex: filter, reduce, map
    
            this.setState({ completeAndActiveTaskList });
        }, 0);
    

    如果您想获得完整或活跃的列表,也很容易获得。 更多示例请参考示例代码=>https://github.com/jeffnian88/redux-livequery-todos-example

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-17
      • 1970-01-01
      • 2017-06-03
      • 2020-05-05
      • 2020-02-16
      • 2019-12-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多