Redux 入門 Demo:TodoList

Ozzie發表於2020-04-03

Demo說明

  1. 本次 Demo 使用了 antd 呈現樣式,需要透過 npm install antd --save 安裝該元件庫
  2. 在使用 Redux 之前,需要安裝 redux,可以用 npm install redux --save 安裝

程式碼以及說明

需熟記的圖

結果樣式

程式碼

  1. 目錄結構

     |-src
       |-index.js
       |-TodoList.js
       |-store
         |-index.js
         |-reducer.js
  2. /src/index.js

     //渲染頁面
    
     import React from 'react'
     import ReactDOM from 'react-dom'
     //引入TodoList元件
     import TodoList from './TodoList'
    
     ReactDOM.render(
       <TodoList />,
       document.getElementById('root')
     )
  3. /src/TodoList.js

     //TodoList元件
    
     import React, { Component } from 'react'
     //引入store
     import store from './store/index'
     //引入antd樣式
     import 'antd/dist/antd.css'
     //按需引入輸入框、按鈕、列表項樣式
     import { Input, Button, List } from 'antd'
    
     class TodoList extends Component {
       constructor(props) {
         super(props);
         //透過store的getState方法獲取公共資料
         this.state = store.getState();
         this.handleInputChange = this.handleInputChange.bind(this);
         this.handleStoreChange = this.handleStoreChange.bind(this);
         this.handleBtnClick = this.handleBtnClick.bind(this);
         //只要store發生改變,就立即執行handleStoreChange這個方法
         store.subscribe(this.handleStoreChange);
       }
    
       render() {
         return (
           <div style={{marginLeft:'30px', marginTop:'10px'}}>
             <div>
               <Input placeholder='TodoList'
                      style={{width:'300px', marginRight:'10px'}}
                      value={this.state.inputValue}
                      onChange={this.handleInputChange}
               />
               <Button type='primary'
                       onClick={this.handleBtnClick}
               >
                 提交
               </Button>
             </div>
             <List bordered
                   style={{marginTop:'10px', width:'500px'}}
                   dataSource={this.state.list}
                   renderItem={
                     (item, index) => (
                       <List.Item onClick={this.handleItemDelete.bind(this, index)}>
                           {item}
                       </List.Item>
                     )
                   }
             />
           </div>
         )
       }
    
       handleInputChange(e) {  //觸發功能:改變輸入框值
         const action = {  //將此需求用action表達
           type: 'CHANGE_INPUT_VALUE',  //type即需求的描述,
           value: e.target.value  //value是要傳給sotre的值
         };
         store.dispatch(action);  //將action傳給store,store會自動將action物件的內容傳給reducer
       };
    
       handleStoreChange() {  //得到新的state值
         //用getState方法得到最新的store資料,並通知setState更新
         this.setState(store.getState())
       };
    
       handleBtnClick() {  //觸發功能:增加列表項
         const action = {
           type: 'ADD_LIST'
         };
         store.dispatch(action)  //傳給Store
       };
    
       handleItemDelete(index) {  // 觸發功能:刪除列表項
         const action = {
           type: 'DELETE_LIST',
           value: index
         }
         store.dispatch(action)  //傳給store
       }
     }
    
     export default TodoList;
  4. /src/store/index.js

     //Store部分
    
     //從redux中引入createStore方法
     import { createStore } from 'redux';
     //引入Reducer
     import reducer from './reducer';
    
     const store = createStore(  //透過createStore()建立一個公共的資料儲存倉
       reducer,  // 將reducer部分傳遞給store
     );
    
     export default store;
  5. /src/store/reducer.js

     //Reducer部分
    
     const defaultState = {
       inputValue: '',
       list: [1, 2, 3]
     };
    
     //reducer負責管理整個store中的資料
     //reducer裡面必須返回一個函式,引數中的state就是整個store倉庫裡儲存的資料
     export default (state = defaultState, action) =>  {  //state先定為defaultState
       if(action.type === 'changeInputValue') {  //若需觸發改變輸入框值的功能
         //深複製state
         const newState = JSON.parse(JSON.stringify(state));
         //改變state的輸入框值,即是改變渲染的輸入框值
         newState.inputValue = action.value;
         //由於reducer不能直接修改傳遞過來的state,所以透過newState返回修改的結果
         // store會根據newState替換原來的state值
         return newState;
       }
       if(action.type === 'addTodoItem') {  //若需觸發增加列表項的功能
         const newState = JSON.parse(JSON.stringify(state));  //深複製state
         newState.list.push(newState.inputValue);  //新增列表項
         newState.inputValue = '';  //清空輸入欄
         return newState;
       }
       if(action.type === 'DELETE_LIST') {  //若需觸發刪除列表項的功能
         const newState = JSON.parse(JSON.stringify(state));  //深複製state
         newState.list.splice(action.value, 1);  //刪除列表項
         return newState;
       }
       return state;
     }

最佳化

  • 之前寫 action 的時候,我們會發現諸多不便,比如 type 很容易寫錯之類的,不如將 action 部分拆分出來,這會提高程式碼的質量,步驟如下:
  1. 新建檔案:

    • /src/store/actionTypes.js:用來單獨儲存 action 的 type
    • /src/store/actionCreators.js:用來封裝 action 的建立
  2. 程式碼:

     /src/store/actionTypes.js
    
     export const CHANGE_INPUT_VALUE = 'change_input_value';
     export const ADD_LIST = 'add_list';
     export const DELETE_LIST = 'delete_list';
     /src/store/actionCreators.js
    
     import { CHANGE_INPUT_VALUE, ADD_LIST, DELETE_LIST } from './actionTypes';
    
     export const changeInputValueAction = (value) => ({
       type: CHANGE_INPUT_VALUE,
       value
     });
     export const addListAction = () => ({
       type: ADD_LIST
     });
     export const deleteListAction = (index) => ({
       type: DELETE_LIST,
       index
     })
  3. 那麼,/src/TodoList.js 可以改為:

     import React, { Component } from 'react';
     import store from './store/index';
     import 'antd/dist/antd.css';
     import { Input, Button, List } from 'antd';
     //引入建立的 action
     import { changeInputValueAction, addListAction, deleteListAction } from './store/actionCreators';
    
     class TodoList extends Component {
       constructor(props) {
         super(props);
         this.state = store.getState();
         this.handleInputChange = this.handleInputChange.bind(this);
         this.handleBtnClick = this.handleBtnClick.bind(this);
         this.handleStoreChange = this.handleStoreChange.bind(this);
         store.subscribe(this.handleStoreChange);
       }
    
       render() {
         return (
           <div style={{marginLeft:'30px', marginTop:'20px'}}>
             <div>
               <Input placeholder='TodoList'
                      style={{width:'300px', marginRight:'10px'}}
                      value={this.state.inputValue}
                      onChange={this.handleInputChange}
               />
               <Button type='primary'
                       onClick={this.handleBtnClick}
               >
                 提交
               </Button>
             </div>
             <List bordered
                   style={{marginTop:'10px', width:'500px'}}
                   dataSource={this.state.list}
                   renderItem={
                     (item, index) => (
                       <List.Item onClick={this.handleItemDelete.bind(this, index)}>
                           {item}
                       </List.Item>
                     )
                   }
             />
           </div>
         )
       }
    
       handleInputChange(e) {
         //直接使用 changeInputValueAction,並傳入引數 e.target.value
         const action = changeInputValueAction(e.target.value);
         store.dispatch(action);
       }
    
       handleBtnClick() {
         //直接使用 addListAction 這個 action
         const action = addListAction();
         store.dispatch(action)
       }
    
       handleStoreChange() {
         this.setState(store.getState())
       }
    
       handleItemDelete(index) {
         //直接使用 deleteListAction 這個 action
         const action = deleteListAction(index);
         store.dispatch(action)
       }
     }
    
     export default TodoList;
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章