React/Redux—组件的每个实例的单独状态

React/Redux—Individual state for each Instance of a Component

如果有一个用户列表并且每个条目都有一个按钮 »EDIT«。如果用户点击它,会发生以下情况:

  1. 请求表单的服务器
  2. 将组件<UserEditForm />添加到条目中,什么扩展了条目

除一件事外,这工作正常:如果单击更多按钮,则每个表单实例都会收到请求的最后一个用户表单的数据。那是因为我在州里只有一个userform属性

所以为了解决这个问题,我想将 userform 交换为 userforms,其中 should/could 是一个像这样的对象:

userforms: {
  <id of first instance>: { … }, //formdata
  <id of second instance>: { … },
  …
}

但由于我是 React/Redux 的新手,所以我真的不知道该怎么做,或者真正做到这一点的 »正确« 方法或最佳实践是什么。

我的想法是像这样创建一个更高阶的组件:

import React from 'react';
import {connect} from 'react-redux';
import {uuid} from '../../helpers/uuid';

export const formdatainstance = (FormInstance) => {

  let id = null;

  class FormDataMapper extends React.Component {
    constructor (props) {
      super(props);
      id = uuid();
    }

    render () {
      //extract the formdata from the state
      //using the id
      return <FormInstance { ...this.props } />
    }
  }

  const mapStateToProps = (state) => {
    console.log(id); //is null for one run
    return {
      userforms: state.userforms
    };
  };

  return connect(mapStateToProps)(FormDataMapper);
}

所以在列表组件中我可以:

import UserEditForm from './UserEditForm';
import {formdatainstance} from './formdatainstance';

const MappedUserEditForm = formdatainstance(UserEditForm);

class List extends React.Component {
  render(){
    return (
      {users.map(user => {
        //more stuff
        <MappedUserEditForm />
        //more stuff
      })}
    );
  }
}

所以我的问题是:这是个好主意吗?如果是的话,进行清理的正确方法是什么,那么我应该在组件的生命周期中什么时候从状态中删除数据?有没有其他方法可以做到这一点,哪个更容易?

感谢您的帮助!

您可以执行以下操作...

import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';

class UserEditForm extends Component {
   ...

   render() {
      return <form onSubmit={this.props.handleSubmit(this.props.onSubmit)}>
          ...form fields
      </form>
   }
}

const mapStateToProps = (state, ownProps) => {
   return {
      form: ownProps.formId
   }
}

export default compose(
   connect(mapStateToProps),
   reduxForm({
      //...other redux-form options
   })
)(UserEditForm);

您的 ListComponent

render() {
   return <ul>
      {this.props.users.map(user => 
         <li key={user.id}>
             ...
             <UserEditForm formId={'form-' + user.id} onSubmit={...} />
         </li>
      )}
   </ul>
}

这样您就可以拥有一个动态的表单名称。

即使@jpdelatorre 的答案对我来说似乎是最好的,因为它还包括 link 到 redux-forms,这可能对我有很大帮助,我想 post 我的工作解决方案在这里,以防万一有人发现它有用。一夜之间就被它击中了,所以需要测试我的想法是否正确,我最终可以证明什么。

我无法用一个单一的 HOC 完成整个映射,我也需要 add/modify 减速器。基本上它是这样工作的:

  1. 数据映射是通过ID来完成的,

  2. 包装原始动作创建者,以便将使用的 id 附加到对象

  3. reducer 被包装成两个并由 »datamapped« reducer 调用

原来reducer和action creator的代码不用改,wrapping类的好用。我首先想使用动态创建的 uuid,但我放弃了它,以便可以保存和恢复整个应用程序状态。

所以 HOC 代码是:

import React from 'react';
import {connect} from 'react-redux';

// The Component to wrap,
// all of its actions
// its default state
export const formdatainstance = (FormInstance, Actions, defaultState = {}) => {

  const mapStateToProps = (state) => {
    return {
      mappedData: state.mappedData
    };
  };

  class FormDataMapper extends React.Component {
    static propTypes = {
      id: React.PropTypes.string.isRequired
    };

    static contextTypes = {
      store: React.PropTypes.object
    };

    //most of mapping happens here
    render () {
      //wrap the action creators
      const actions = Object.keys(Actions).reduce((list, key) =>{
        list[key] = (...args) => {
          const action = Actions[key](...args);

          //handle asyn operations as well
          if('then' in action && typeof action['then'] == 'function') {
            action.then(data => {
              //attaching the id
              this.props.dispatch({...data, id: this.props.id});
            });
          } else {
            //attach the id
            this.context.store.dispatch({...action, id: this.props.id });
          }
        };
        return list;
      }, {}),
        //there wont be any data at first, so the default state is handed
        //over
        mappedProps = this.props.mappedData.hasOwnProperty(this.props.id) ?
          this.props.mappedData[this.props.id] : defaultState;

      //merge the hotchpotch
      let props = Object.assign({}, mappedProps, this.props, actions);

      //clean up
      delete props.id;
      delete props.mappedData;

      return <FormInstance { ...props } />
    }
  }

  return connect(mapStateToProps)(FormDataMapper);
};

reducer代码:

//hlper method
export const createTypesToReducerMap = (types, reducer) => {
  return Object.keys(types).reduce((map, key) => {
    map[types[key]] = reducer;
    return map;
  }, {});
}


export const createMappedReducer = (reducerMap, defaultState = {}) => {
  const HANDLERS = reducerMap.reduce((handlers, typeMap) => {
    return { ...handlers, ...typeMap };
  },{});

  return (state, action) => {

    if (!action.hasOwnProperty('id')) {
      if (state === undefined) return defaultState;
      return state;
    }

    const reducer = HANDLERS.hasOwnProperty(action.type) ?
          HANDLERS[action.type] : null;

    let a = {...action};
    delete a.id;


    return reducer !== null ?
        Object.assign({}, state, { [action.id]: reducer(state[action.id], a)}) :
        state;
  }
}

最后是商店:

const userEditTypeReducerMap = createTypesToReducerMap(userEditTypes, userFormReducer);


const reducer = combineReducers({
  …
  mappedData: createMappedReducer(
    [userEditTypeReducerMap], {})
  …
});


export default compose(
  applyMiddleware(
    thunk
  )
)(createStore)(reducer, {});