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

ewm0tg9j  于 2024-01-08  发布在  React
关注(0)|答案(5)|浏览(133)

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

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

问题详情:

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

zazmityj

zazmityj1#

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

  1. const mapStateToProps = (state, ownProps) {
  2. let myState = state[ownProps.namespace]
  3. return {
  4. myState.value
  5. }
  6. }

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

  1. const mapDispatchToProps = (dispatch, ownProps) {
  2. return {
  3. myAction: (myParam) => dispatch(myAction(ownProps.namespace, myParam))
  4. }
  5. }


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

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


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

  1. const myReducer = (namespace) => (state = initialState, action) => {
  2. switch(action.type) {
  3. case `${namespace}/${MY_TYPE_CONSTANT}`:
  4. return { ...state, action.myParam }
  5. default:
  6. return state
  7. {
  8. }


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

  1. combineReducers({
  2. myInstance1 : myReducer('myInstance1')
  3. myInstance2 : myReducer('myInstance2')
  4. }


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

  1. render() {
  2. return (
  3. <div>
  4. <MyComponent namespace='myInstance1' />
  5. <MyComponent namespace='myInstance2' />
  6. </div>
  7. )
  8. }

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

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

  1. const reducer = combineReducers({
  2. myInstance1: namespaced('myInstance1')(myReducer)
  3. myInstance2: namespaced('myInstance2')(myReducer)
  4. })


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

  1. render() {
  2. return (
  3. <div>
  4. <SubspaceProvider mapState={state => state.myInstance1} namespace='myInstance1'>
  5. <MyComponent />
  6. </SubspaceProvider>
  7. <SubspaceProvider mapState={state => state.myInstance2} namespace='myInstance2'>
  8. <MyComponent />
  9. </SubspaceProvider>
  10. </div>
  11. )
  12. }


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

  1. const mapStateToProps = (state) {
  2. return {
  3. state.value
  4. }
  5. }


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

展开查看全部
9gm1akwq

9gm1akwq2#

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

webghufk

webghufk3#

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

  1. createSlice({
  2. name: "counters",
  3. initialState: {},
  4. reducers: {}
  5. })

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

  1. createSlice({
  2. name: "counters",
  3. initialState: {},
  4. reducers: {
  5. // will be use on mounting of the component
  6. createState(state, { payload: id }: { payload: number | string }) {
  7. // create state only if there's no state for the id
  8. if (!state[id]) {
  9. state[id] = 0; // this is where you actually define the initial state instead of up there 👆...
  10. }
  11. },
  12. deleteState(state, { payload: id }: { payload: number | string }) {
  13. delete state[id];
  14. },
  15. }
  16. })


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

  1. import { createSlice } from '@reduxjs/toolkit';
  2. import { IRootState } from '../store';
  3. export const {
  4. reducer: countersReducer,
  5. actions: {
  6. increment, decrement,
  7. createState: createCounterState,
  8. deleteState: deleteCounterState },
  9. } = createSlice({
  10. name: 'counter',
  11. initialState: {},
  12. reducers: {
  13. createState(state, { payload: id }: { payload: number | string }) {
  14. // create state only if there's no state for the id
  15. if (!state[id]) {
  16. state[id] = 0; // this is where you actually define the initial state instead of up there 👆...
  17. }
  18. },
  19. deleteState(state, { payload: id }: { payload: number | string }) {
  20. delete state[id];
  21. },
  22. increment(state, { payload: id }: { payload: number | string }) {
  23. state[id] = state[id] + 1;
  24. },
  25. decrement(state, { payload: id }: { payload: number | string }) {
  26. state[id] = state[id] - 1;
  27. },
  28. },
  29. });
  30. export const selectCounters = (state: IRootState) => state.counters;


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

  1. <Counter />接受id prop以区分计数器
  1. import { useEffect } from 'react';
  2. import { useDispatch, useSelector } from 'react-redux';
  3. import {
  4. createCounterState,
  5. decrement,
  6. increment,
  7. selectCounters,
  8. deleteCounterState,
  9. } from './countReducer';
  10. export const Counter = ({ id }: { id: number | string }) => {
  11. const counters = useSelector(selectCounters);
  12. const dispatch = useDispatch();
  13. useEffect(() => {
  14. dispatch(createCounterState(id));
  15. return () => {
  16. dispatch(deleteCounterState());
  17. };
  18. }, []);
  19. return (
  20. <div style={{ display: 'flex', gap: '8px', fontSize: '20px' }}>
  21. <button type="button" onClick={() => dispatch(decrement(id))}>
  22. -
  23. </button>
  24. {counters[id]}
  25. <button type="button" onClick={() => dispatch(increment(id))}>
  26. +
  27. </button>
  28. </div>
  29. );
  30. };


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

  1. export const App: FC<{ name: string }> = ({ name }) => {
  2. return (
  3. <Provider store={store}>
  4. {/** You can pass a numeric or string id */}
  5. <Counter id={1} />
  6. <br />
  7. <Counter id={'foo'} />
  8. <br />
  9. <Counter id={'bar'} />
  10. <br />
  11. <Counter id={10} />
  12. </Provider>
  13. );
  14. };


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

展开查看全部
92vpleto

92vpleto4#

我把这个问题解释为:

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

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

  1. {
  2. sections: {
  3. 0: {
  4. header: 'My section title',
  5. content: 'Whatever your content is'
  6. },
  7. 1: {
  8. header: 'My other section title',
  9. content: 'Loads of lovely writing or hrefs to images or whatever'
  10. }
  11. }
  12. }

字符串

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

{sections[2].header}

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

展开查看全部
eagi6jfj

eagi6jfj5#

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

相关问题