在同一页面/路由上有多个相同的可重用Redux React组件示例

ewm0tg9j  于 11个月前  发布在  React
关注(0)|答案(5)|浏览(97)

我们正在创建一个大型前端应用程序。
我们正在使用React-Redux
我们正在创建一些可重用的组件。

  • 这个问题是关于在同一个页面/路由上有多个相同的可重用redux react组件的示例 *

问题详情:

我们有一个Sectionheader组件。它绑定到redux状态。
它监听头属性reducer SectionheaderReducer
因为我们在页面上有两个Sectionheader的示例,它们都倾向于显示相同的值,因为它们被绑定到相同的存储状态属性。
如何使基于redux的可重用react组件可配置?这样每个示例都可以为reducer SectionheaderReducer提供不同的header属性值

zazmityj

zazmityj1#

你需要实现一些命名示例的方法,这可以是基本的,比如传入一个键来区分组件和reducer。
可以在mapStateToProps函数中使用ownProps来引导到命名空间的Map

const mapStateToProps = (state, ownProps) {
    let myState = state[ownProps.namespace]
    return {
        myState.value
    }
}

字符串
可以使用相同的方法将命名空间传递给mapDispatchToProps

const mapDispatchToProps = (dispatch, ownProps) {
    return {
        myAction: (myParam) => dispatch(myAction(ownProps.namespace, myParam))
    }
}


只要记住在操作类型中使用名称空间,这样reducer就不会太过麻烦

const myAction => (namespace, myParam) {
    return { type: `${namespace}/${MY_TYPE_CONSTANT}`, myParam }
}


并确保减速器也是命名空间的

const myReducer = (namespace) => (state = initialState, action) => {
    switch(action.type) {
        case `${namespace}/${MY_TYPE_CONSTANT}`:
            return { ...state, action.myParam }
        default:
            return state
    {
}


现在,在组合缩减器时添加2个命名空间缩减器

combineReducers({
    myInstance1 : myReducer('myInstance1')
    myInstance2 : myReducer('myInstance2')
}


最后将命名空间传递给每个示例

render() {
    return (
        <div>
            <MyComponent namespace='myInstance1' />
            <MyComponent namespace='myInstance2' />
        </div>
    )
}

**免责声明:**我是以下库的主要贡献者。

redux-subspace可以提供更高级的命名空间实现,而不必为每个希望拥有多个示例的组件重新实现此模式。
创建缩减器的步骤与上述类似

const reducer = combineReducers({ 
    myInstance1: namespaced('myInstance1')(myReducer)
    myInstance2: namespaced('myInstance2')(myReducer)
})


然后,可以使用SubspaceProvider来切换出每个组件的状态

render() {
    return (
        <div>
            <SubspaceProvider mapState={state => state.myInstance1} namespace='myInstance1'>
                <MyComponent />
            </SubspaceProvider>
            <SubspaceProvider mapState={state => state.myInstance2} namespace='myInstance2'>
                <MyComponent />
            </SubspaceProvider>
        </div>
    )
}


只需确保您还将mapStateToProps函数更改为,以便从提供程序中Map的子树开始遍历

const mapStateToProps = (state) {
    return {
        state.value
    }
}


如果您希望减少嵌套,还可以使用“高阶组件”。

9gm1akwq

9gm1akwq2#

我用不同的方式实现了它,实际上没有用名称空间更改操作名称。
相反,我添加了infra函数,它将拦截动作创建者并将meta-data添加到每个动作中。(在FSA之后)这样你就不需要改变你的reducer或mapStateToProps函数。
它还与redux-thunk兼容。
应该很容易使用... reducer-action-interceptor

webghufk

webghufk3#

如果你正在使用Redux Toolkit,你可以考虑这个模式。你的createSliceinitialState将是一个对象,一个空对象开始。

createSlice({
   name: "counters",
   initialState: {},
   reducers: {}
})

字符串
然后,您将有几个reducer来创建和删除初始状态(这将在组件的挂载和卸载时触发)

createSlice({
   name: "counters",
   initialState: {},
   reducers: {
    // will be use on mounting of the component
    createState(state, { payload: id }: { payload: number | string }) {
      // create state only if there's no state for the id
      if (!state[id]) {
        state[id] = 0; // this is where you actually define the initial state instead of up there 👆...
      }
    },
    deleteState(state, { payload: id }: { payload: number | string }) {
      delete state[id];
    },
   }
})


你可以看看this file on Stackblitz的完整代码。
接下来,我们将添加递增和递减操作以及一个名为selectCountersselector function

import { createSlice } from '@reduxjs/toolkit';
import { IRootState } from '../store';

export const {
  reducer: countersReducer,
  actions: { 
    increment, decrement, 
    createState: createCounterState, 
    deleteState: deleteCounterState },
} = createSlice({
  name: 'counter',
  initialState: {},
  reducers: {
    createState(state, { payload: id }: { payload: number | string }) {
      // create state only if there's no state for the id
      if (!state[id]) {
        state[id] = 0; // this is where you actually define the initial state instead of up there 👆...
      }
    },
    deleteState(state, { payload: id }: { payload: number | string }) {
      delete state[id];
    },
    increment(state, { payload: id }: { payload: number | string }) {
      state[id] = state[id] + 1;
    },
    decrement(state, { payload: id }: { payload: number | string }) {
      state[id] = state[id] - 1;
    },
  },
});

export const selectCounters = (state: IRootState) => state.counters;


接下来,让我们处理集成的Counter component。请注意以下两点:
1.我们在安装时创建组件状态,即在useEffect内部,并在卸载时删除它。

  1. <Counter />接受id prop以区分计数器
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
  createCounterState,
  decrement,
  increment,
  selectCounters,
  deleteCounterState,
} from './countReducer';

export const Counter = ({ id }: { id: number | string }) => {
  const counters = useSelector(selectCounters);

  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(createCounterState(id));
    return () => {
      dispatch(deleteCounterState());
    };
  }, []);

  return (
    <div style={{ display: 'flex', gap: '8px', fontSize: '20px' }}>
      <button type="button" onClick={() => dispatch(decrement(id))}>
        -
      </button>
      {counters[id]}
      <button type="button" onClick={() => dispatch(increment(id))}>
        +
      </button>
    </div>
  );
};


就是这样!!现在,我们可以多次使用re-usable <Counter id={"hello or any other number or string id"} />

export const App: FC<{ name: string }> = ({ name }) => {
  return (
    <Provider store={store}>
      {/** You can pass a numeric or string id */}
      <Counter id={1} />
      <br />
      <Counter id={'foo'} />
      <br />
      <Counter id={'bar'} />
      <br />
      <Counter id={10} />
    </Provider>
  );
};


下面是Stackblitz上的工作示例:https://stackblitz.com/edit/stackblitz-starters-68admb?file=src%2FApp.tsx
x1c 0d1x的数据

92vpleto

92vpleto4#

我把这个问题解释为:

  • 您在商店中有内容数据(例如,部分及其标题)
  • 你有绘制数据位的组件(例如你的<SectionHeader />
  • 你想在一个页面上显示多个部分,但当前所有的标题都有相同的文本

一个可能的解决方案是将“section”的概念添加到存储中。您可以创建管理数据内容结构的reducer。例如,存储状态可能看起来像这样:

{ 
  sections: {
     0: {
        header: 'My section title',
        content: 'Whatever your content is'
     },
     1: {
        header: 'My other section title',
        content: 'Loads of lovely writing or hrefs to images or whatever'
     }
  }
}

字符串

这样就有了一个“容器组件”、“布局组件”或“智能组件”(它们有很多名字),它“知道”你想在特定页面上使用第2节和第4节。它如何知道这一点,取决于你。也许你硬编码的索引(因为它总是相同的),也许你有一个过滤规则,也许你在商店里有另一个定义选择的字段......等等。
然后容器组件会将选定的标题传递给“dumb”,可能像这样:

{sections[2].header}

型
或

<SectionHeader title={sections[2].header} />

eagi6jfj

eagi6jfj5#

将我们的组件转换为哑(无状态)组件,以便这些组件可以轻松地重用,而不会产生任何并发症。

相关问题