#17 React-redux 实现用户列表的显示、增加、删除


  • 1
    administrators

    直接使用在 实现 Users Reducer 中实现的 userReducer。用 react-redux 完成 UserListUser 组件,可以对用户列表进行显示、增加、删除操作。

    你不需要实现 store 的生成和使用 Provider,只需要完成 connect 的过程和组件的实现。

    (留意 <input type="number" /> 的字符串和数字的转换问题)


  • 0

    示例里:

    class UsersList extends Component {
      render (
        <div>
          {/* 输入用户信息,点击“新增”按钮可以增加用户 */}
          <div className='add-user'>
            <div>Username: <input type='text' /></div>
            <div>Age: <input type='number' /></div>
            <div>Gender:
              <label>Male: <input type='radio' name='gender' value='male' /></label>
              <label>Female: <input type='radio' name='gender' value='female' /></label>
            </div>
            <button>增加</button>
          </div>
          {/* 显示用户列表 */}
          <div className='users-list'>{/* TODO */}</div>
        </div>
      )
    }
    

    应该改一下:

    class UsersList extends Component {
      render () {
        return (
          // blabla...
        )
      }
    }
    

  • 0
    administrators

    @Sunjourney 感谢指出,一改


  • 0
    管理员

    class User extends Component {
      render () {
        const { user, onDeleteUser, index} = this.props
        return (
          <div>
            <div>Name: {user.userName}</div>
            <div>Age: {user.age}</div>
            <div>Gender: {user.gender}</div>
            <button onClick={()=> {onDeleteUser(user, index)}}>删除</button>
          </div>
        )
      }
    }
    
    class UsersList extends Component {
      constructor() {
        super();
        this.state = {
          index: 0,
          userName: '',
          age: '',
          gender: ''
        }
      }
      
      render () {
        const { userName, age, gender } = this.state
        const { onAddUser, onDeleteUser, users } = this.props
    
        return (
          <div>
            {/* 输入用户信息,点击“新增”按钮可以增加用户 */}
            <div className='add-user'>
              <div>Username: <input type='text' value={userName} onChange={e=>{this.setState({userName: e.target.value})}} /></div>
              <div>Age: <input type='number' value={age} onChange={e=>{this.setState({age: e.target.value})}} /></div>
              <div>Gender:
                <label>Male: <input type='radio' name='gender' value='male' onChange={e=>{this.setState({gender: e.target.value})}} /></label>
                <label>Female: <input type='radio' name='gender' value='female' onChange={e=>{this.setState({gender: e.target.value})}} /></label>
              </div>
              <button onClick={()=>{this.setState({index: this.state.index+1});onAddUser(this.state)}}>增加</button>
            </div>
            {/* 显示用户列表 */}
            <div className='users-list'>
              {users.map((user, index)=>
                <User user={user} onDeleteUser={onDeleteUser.bind(this)} index={index} key={index}/>
              )}
            </div>
          </div>
        )
      }
    }
    
    
    /**
     * 环境中已经注入了 React-redux,你可以直接使用 connect,或者 ReactRedux.connect
     */
    const mapStateToProps = (state) => {
      return {
        users: state   
      }
    }
    
    const mapDispatchToProps = (dispatch) => {
      return {
        addUser: (user) => {
          dispatch({
            type: 'ADD_USER',
            user
          })
        },
        deleteUser: (user, index) => {
          console.log(index)
          dispatch({
            type: 'DELETE_USER',
            index: index,
            user
          })
        }
      }
    }
    
    class UsersContainer extends Component {
    
      render () {
        return (
          <UsersList
            users={this.props.users}
            onAddUser={this.props.addUser.bind(this)}
            onDeleteUser={this.props.deleteUser.bind(this)}
          />
        )
      }
    }
    
    const usersReducer = connect(
      mapStateToProps,
      mapDispatchToProps
    )(UsersContainer)

  • 0
    administrators

    @陈小俊

    connect 的时候需要覆盖的类变量,下面的写法是不正确的。

    const usersReducer = connect(
    mapStateToProps,
    mapDispatchToProps
    )(UsersContainer)

    写法应该类似:

    const UsersList = connect(
       mapStateToProps,
       mapDispatchToProps
    )(UsersList)
    

  • 0
    管理员

    @胡子大哈 我忘记引入16题的userReducer了,这次这样写了,还是有错哈。。

    const ADD_USER = "ADD_USER"
    const DELETE_USER = "DELETE_USER"
    const UPDATE_USER = "UPDATE_USER"
    
    const usersReducer = (state=[],action) => {
      switch(action.type){
        case ADD_USER:
          return [...state, action.user]
        case DELETE_USER:
          return[...state.slice(0,action.index), ...state.slice(action.index+1)]
        case UPDATE_USER:
          return [...state.map((user, index) => {
            if (index === action.index) {
              return {...user, ...action.user}
            } else {
              return user 
            }
          })]
        default:
          return state
      }
    }
    
    class User extends Component {
      render () {
        const { user, deleteUser, index} = this.props
        return (
          <div>
            <div>Name: {user.userName}</div>
            <div>Age: {user.age}</div>
            <div>Gender: {user.gender}</div>
            <button onClick={()=> {deleteUser(user, index)}}>删除</button>
          </div>
        )
      }
    }
    
    class UsersList extends Component {
      constructor() {
        super();
        this.state = {
          index: 0,
          userName: '',
          age: '',
          gender: ''
        }
      }
      
      render () {
        const { userName, age, gender } = this.state
        const { addUser, deleteUser, users } = this.props
    
        return (
          <div>
            {/* 输入用户信息,点击“新增”按钮可以增加用户 */}
            <div className='add-user'>
              <div>Username: <input type='text' value={userName} onChange={e=>{this.setState({userName: e.target.value})}} /></div>
              <div>Age: <input type='number' value={age} onChange={e=>{this.setState({age: e.target.value})}} /></div>
              <div>Gender:
                <label>Male: <input type='radio' name='gender' value='male' onChange={e=>{this.setState({gender: e.target.value})}} /></label>
                <label>Female: <input type='radio' name='gender' value='female' onChange={e=>{this.setState({gender: e.target.value})}} /></label>
              </div>
              <button onClick={()=>{this.setState({index: this.state.index+1});addUser(this.state)}}>增加</button>
            </div>
            {/* 显示用户列表 */}
            <div className='users-list'>
              {users.map((user, index)=>
                <User user={user} deleteUser={deleteUser} index={index} key={index}/>
              )}
            </div>
          </div>
        )
      }
    }
    
    
    /**
     * 环境中已经注入了 React-redux,你可以直接使用 connect,或者 ReactRedux.connect
     */
    const mapStateToProps = (state) => {
      return {
        users: state   
      }
    }
    
    const mapDispatchToProps = (dispatch) => {
      return {
        addUser: (user) => {
          dispatch({
            type: ADD_USER,
            user
          })
        },
        deleteUser: (user, index) => {
          dispatch({
            type: DELETE_USER,
            index: index,
            user
          })
        }
      }
    }
    
    UsersList = connect(
      mapStateToProps,
      mapDispatchToProps
    )(UsersList)
    

  • 0
    administrators

    @陈小俊

    1. 删除用户的时候不需要传入 user,那个是多余的。
    2. userName 应该是 username
    3. age 要转换成数字,否则从 input 里面获取的时候是字符串
    4. 增加用户的时候不需要传入 index,那个也是不需要的。

  • 3
    管理员

    哈~谢谢大哈哥。
    这道题注意的地方挺多的,不愧是全网通过率最低的题啊,大家都在试错呀!

    最终代码分享下:
    input取值的方式不太好,有没人做下优化

    const ADD_USER = "ADD_USER"
    const DELETE_USER = "DELETE_USER"
    const UPDATE_USER = "UPDATE_USER"
    
    //需要引入16题的usersReducer
    const usersReducer =(state=[],action) => {
      switch(action.type){
        case ADD_USER:
          return [...state, action.user]
        case DELETE_USER:
          return [...state.slice(0,action.index), ...state.slice(action.index+1)]
        case UPDATE_USER:
          return [...state.map((user, index) => {
            if (index === action.index) {
              return {...user, ...action.user}
            } else {
              return user 
            }
          })]
        default:
          return state
      }
    }
    
    class User extends Component {
      render () {
        const { user, deleteUser, index} = this.props
        return (
          <div>
            <div>Name: {user.username}</div>
            <div>Age: {user.age}</div>
            <div>Gender: {user.gender}</div>
            <button onClick={()=> {deleteUser(index)}}>删除</button>
          </div>
        )
      }
    }
    
    class UsersList extends Component {
      constructor() {
        super();
        this.state = {
          username: '',
          age: '',
          gender: ''
        }
      }
      
      render () {
        const { username, age, gender } = this.state
        const { addUser, deleteUser, users } = this.props
        return (
          <div>
            <div className='add-user'>
              <div>Username: <input type='text' value={username} onChange={e=>{this.setState({username: e.target.value})}} /></div>
              <div>Age: <input type='number' value={age} onChange={e=>{this.setState({age: parseInt(e.target.value)})}} /></div>
              <div>Gender:
                <label>Male: <input type='radio' name='gender' value='male' onChange={e=>{this.setState({gender: e.target.value})}} /></label>
                <label>Female: <input type='radio' name='gender' value='female' onChange={e=>{this.setState({gender: e.target.value})}} /></label>
              </div>
              <button onClick={()=>addUser(this.state)}>增加</button>
            </div>
            <div className='users-list'>
              {users.map((user, index)=>
                <User user={user} deleteUser={deleteUser} index={index} key={index}/>
              )}
            </div>
          </div>
        )
      }
    }
    
    //需要注入的props
    const mapStateToProps = (state) => {
      return {
        users: state   
      }
    }
    
    //需要注入的函数
    const mapDispatchToProps = (dispatch) => {
      return {
        addUser: (user) => {
          dispatch({
            type: ADD_USER,
            user
          })
        },
        deleteUser: (index) => {
          dispatch({
            type: DELETE_USER,
            index: index
          })
        }
      }
    }
    
    //通过connect高阶组件,将需要的值或者函数作为props传入
    UsersList = connect(
      mapStateToProps,
      mapDispatchToProps
    )(UsersList)
    

  • 0

    @陈小俊 你可以从radio的上层div使用onChange,可以冒泡获取radio的value,这样就不用调用两次onChange


  • 0

    const usersReducer = (state=[], action) => {
      switch (action.type) {
        case 'ADD_USER': 
          return [...state, action.user];
        case 'DELETE_USER':
          let deState = [...state];
          deState.splice(action.index, 1);
          return [...deState];
        case 'UPDATE_USER':
          let upState = [...state];
          upState[action.index] = {...upState[action.index], ...action.user};
          return [...upState];
        default:
          return state;
          
      }
    }
    
    class User extends Component {
      
      handleDeleteUser () {
        if (this.props.onDeleteUser) {
          this.props.onDeleteUser(this.props.index);
        }
      }
      render () {
        const { user } = this.props;
        return (
          <div>
            <div>Name: {user.username}</div>
            <div>Age: {user.age}</div>
            <div>Gender: {user.gender}</div>
            <button onClick={this.handleDeleteUser.bind(this)}>删除</button>
          </div>
        )
      }
    }
    
    class UsersList extends Component {
      
      constructor() {
        super();
        this.state = {
          username: '',
          age: 0,
          gender: ''
        };
        this.handleInputChange = this.handleInputChange.bind(this);
      }
      
      handleInputChange (e) {
        switch (e.target.type) {
          case 'text':
            this.setState({username: e.target.value});
            break;
          case 'number': 
            this.setState({age: Number(e.target.value)});
            break;
          case 'radio':
            this.setState({gender: e.target.value});
            break;
          default:
            break;
        }
      }
    
    handleAddUser () {
        if (this.props.onAddUser) {
          this.props.onAddUser(this.state);
        }
      }
    
      render () {
        const {users} = this.props;
        return (
          <div>
            {/* 输入用户信息,点击“新增”按钮可以增加用户 */}
            <div className='add-user'>
              <div>Username: <input type='text' onChange={this.handleInputChange} value={this.state.username}/></div>
              <div>Age: <input type='number' onChange={this.handleInputChange} value = {this.state.age}/></div>
              <div>Gender:
                <label>Male: <input type='radio' name='gender' value='male' onChange={this.handleInputChange} /></label>
                <label>Female: <input type='radio' name='gender' value='female' onChange={this.handleInputChange}/></label>
              </div>
              <button onClick={this.handleAddUser.bind(this)}>增加</button>
            </div>
            {/* 显示用户列表 */}
            <div className='users-list'>{users.map((user, index) => <User user={user} key={index} index={index} onDeleteUser={this.props.onDeleteUser}/>)}</div>
          </div>
        )
      }
    }
    
    const mapStateToProps = (state) => {
      return {
        users: state
      }
    }
    const mapDispatchToProps = (dispatch) => {
      return {
        onAddUser: (user) => {
          dispatch({type: 'ADD_USER', user})
        },
        onDeleteUser: (index) => {
          dispatch({type: 'DELETE_USER', index: index});
        }
      }
    }
    UsersList = connect(mapStateToProps, mapDispatchToProps)(UsersList);
     
    

  • 0

    大哈哥,帮我看看为什么会报这样的错嘛,我在我本地也试过了,都没有问题。![alt text](image url0_1496724664285_upload-f4d7f403-b422-4d6b-8769-d383b3763de6 )

    代码是这样的:

    const usersReducer = (state, action) => {
       if(!state) {
           return [];
       }
       switch (action.type) {
           case 'ADD_USER':
               return [...state, action.user];
           case 'DELETE_USER':
               return [...state.slice(0, action.index), ...state.slice(action.index + 1)];
           case 'UPDATE_USER':
               return [...state.map((value,key) => {
                   if(key === action.index){
                       return {...value, ...action.user}
                   }else {
                       return value;
                   }
               })];
           default:
               return state;
       }
    };
    
    const store = createStore(usersReducer);
    
    class User extends Component {
    
       handleDeleteUser (index) {
           if(this.props.deleteUser){
               this.props.deleteUser(index);
           }
       }
    
       render () {
           const { user, index } = this.props;
           return (
               <div>
                   <div>Name: {user.username}</div>
                   <div>Age: {user.age}</div>
                   <div>Gender: {user.gender}</div>
                   <button onClick={this.handleDeleteUser.bind(this, index)}>删除</button>
               </div>
           )
       }
    }
    
    class UsersList extends Component {
       constructor (props) {
           super(props);
           this.state = {
               username: '',
               age: '',
               gender: 'male'
           }
       }
    
       handleAddUser (user) {
    
           if(this.props.addUser) {
               this.props.addUser(user);
           }
       }
    
       render () {
           const {users, deleteUser} = this.props;
           const {username, age, gender} = this.state;
           return (
               <div>
                   {/* 输入用户信息,点击“新增”按钮可以增加用户 */}
                   <div className='add-user'>
                       <div>Username: <input type='text' value={username} onChange={(e) => this.setState({username: e.target.value})} /></div>
                       <div>Age: <input type='number' value={age} onChange={(e) => this.setState({age: parseInt(e.target.value)})} /></div>
                       <div>Gender:
                           <label>Male: <input type='radio' name='gender' value='male' checked={!!(gender === 'male')} onChange={e => this.setState({gender: 'male'})} /></label>
                           <label>Female: <input type='radio' name='gender' value='female' checked={!!( gender === 'female')} onChange={e => this.setState({gender: 'female'})} /></label>
                       </div>
                       <button onClick={this.handleAddUser.bind(this, this.state)}>增加</button>
                   </div>
                   {/* 显示用户列表 */}
                   <div className='users-list'>{
                       users && users.map((value,key) => {
                           return (
                               <User user={value} deleteUser={deleteUser.bind(this)} key={key} index={key}></User>
                           )
                   }) }</div>
               </div>
           )
       }
    }
    
    const mapStateToProps = (state) => {
       return {
           user: state
       }
    };
    
    const mapDispatchToProps = (dispatch) => {
       return {
           addUser : (user) => {
               dispatch({type: 'ADD_USER', user: user})
           },
           deleteUser: (index) => {
               dispatch({type: 'DELETE_USER', index: index})
           },
           // updateUser: (action) => {
           //   dispatch({type: 'UPDATE_USER', index: action.index, user: action.user})
           // }
       }
    };
    
    class UsersContainer extends Component {
       constructor(props){
           super(props);
       }
       render () {
           return (
               <UsersList users={this.props.user} addUser={this.props.addUser.bind(this)} deleteUser={this.props.deleteUser.bind(this)} />
           )
       }
    }
    
    UsersContainer = connect(mapStateToProps, mapDispatchToProps)(UsersContainer);
    

  • 0
    administrators

    @niuniu 首先先把代码用 markdown 格式包裹,不然代码没法看格式了。

    你的代码的问题在于题目要求的是 UserListUser,而你的 connect 的结果是 UsersContainer 这是不符合题意的。


  • 0

    @胡子大哈
    噢噢。我明白了。但是这样写也是可以达到效果的是吧。


  • 0
    administrators

    @niuniu 我不能确定你的代码是否符合要求,只有通过了测试的才能算是完全符合要求。因为有些 bug 用肉眼和手动测试测不出来的。


  • 0

    @胡子大哈
    我在本地测试过了,没有问题,可以添加,删除和显示用户列表的。


  • 0
    administrators

    @niuniu 觉得没有 bug 和真的没有 bug 是不一样的,你看到的不一定是对的(或者在某些情况下可能会不对)。

    所以会有 TDD 和 BDD 来帮助我们覆盖不同的需求和逻辑分支保证真的没有 bug。


  • 0

    @胡子大哈
    噢噢,你说的有道理,那我再看看。谢谢大哈哥


  • 1

    @胡子大哈
    通过测试了,哈哈哈。


  • 0

    这是我只是主要 写了下 connect action的思路,仅此而已.并没有给按钮绑定增删方法。仅供参考,有错误还望支持。

    /**
     * 环境中已经注入了 React-redux,你可以直接使用 connect,或者 ReactRedux.connect
     */
    
    const userReducer = (state={},action)=>{
      switch(action.type){
        case "ADD_ITEM":
          return {...state, userList:[...state.userList,action.item]};
        case "SUBSTRACT_ITEM":
          delete state.userList[action.index];
          return {...state, userList:[...state.userList]};
        default:
          return state;
      }
    }
    
    const store = createStore(userReducer,{});
    
    const Add_item_action = (itemObj)=>return {type:"ADD_ITEM",item:itemObj};
    const Substract_item_action = (index)=>return {type:"SUBSTRACT_ITEM",index:index};
    
    
    class User extends Component {
      render () {
        const { user } = this.props
        return (
          <div>
            <div>Name: {user.username}</div>
            <div>Age: {user.age}</div>
            <div>Gender: {user.gender}</div>
            <button>删除</button>
          </div>
        )
      }
    }
    
    
    const mapPropsToState = (state)=>{
      return {userList:state.userList};
    }
    const mapPropsToDispatch = (dispatch)=>{
      return {
        addHandle:(item)=>dispacth(Add_item_action(item)),
        substructHandle:(index)=>dispatch(Substract_item_action(index))
      }
    }
    
    
    const UserListComponent = connect(mapPropsState,mapPropsToDispatch)(UserList);
    
    class UsersList extends Component {
      render () {
        return (
          <div>
            {/* 输入用户信息,点击“新增”按钮可以增加用户 */}
            <div className='add-user'>
              <div>Username: <input type='text' /></div>
              <div>Age: <input type='number' /></div>
              <div>Gender:
                <label>Male: <input type='radio' name='gender' value='male' /></label>
                <label>Female: <input type='radio' name='gender' value='female' /></label>
              </div>
              <button>增加</button>
            </div>
            {/* 显示用户列表 */}
            <div className='users-list'>
              {
                const that = this;
                return (
                    this.props.userList.mpa((item)=>{
                       return(
                         <User username={item.username} age={item.age} gender={item.gender} />
                       )
                     })
                 )
              }
            </div>
          </div>
        )
      }
    }
    

登录后回复
 

与 ScriptOJ 的连接断开,我们正在尝试重连,请耐心等待