通過 todoList 搞懂 redux

LucineXL發表於2018-09-25

React中的資料傳遞是從根節點開始,一級一級的向下傳遞, 相鄰較遠的元件之間的資料進行交流 就需要一個漫長的傳遞資料的過程, 為了解決這個問題, 便有了 Redux。

簡而言之, Redux 是一個狀態管理工具。 Rudux中有一個儲存資料的 store, 這個 store 是獨立於React元件之外的,每一個元件都可以直接拿到和修改store中的資料, 就不需要把資料傳來傳去了。

我們通過 一個 todoList的demo 來 弄懂 redux。

通過 todoList 搞懂 redux

在開始之前, 首先 先使用 creat-react-app 初始化一個react專案。 為了方便使用, 專案中的元件直接使用 ant design 中元件。

首先, 安裝 redux

yarn add redux react-redux antd -D
複製程式碼

基礎

action

redux 中的 store 是隻讀的。 要想修改 store中的值,只能通過觸發 action 的方式來實現。 action 本質是一個 js 物件, action 內使用 type 欄位來表示 將要執行動作,可以在reducer中針對不同type執行相應的資料操作。

action 建立函式

action 建立函式 是 生成一個 action的方法。

export const ADD_TODO = 'ADD_TODO';
export const DEL_TODO = 'DEL_TODO';

export function addTodo(text) {
    return {
        type: ADD_TODO,
        text
    }
}

export function delTodo(index) {
    return {
        type: DEL_TODO,
        index
    }
}

複製程式碼

在 Rudux 中, 我們可以呼叫 store.dispatch() 方法, 觸發action。

在 React 中, 可以使用 react-redux 中的 connect() 方法來使用。 bindActionCreators() 可以自動把多個 action 建立函式 繫結到 dispatch() 方法上。

reducer

reducer 是一個純函式, 接受 舊的 state 和 action, 最後返回修改後的新 state。

注: 1. 不要直接修改 state。 更新state時, 可以採用 Object.assign({}, {...newState}) 的方式。 2. 在 default 情況下返回舊的 state。遇到未知的 action 時,一定要返回舊的 state。

reducer/list.js

import {
    ADD_TODO,
    DEL_TODO,
} from '../actions';

export function list(state = [], action) {
    switch (action.type) {
        case ADD_TODO: {
            const { text } = action;
            return [...state, text];
        }
        case DEL_TODO: {
            const { index } = action;
            let newState = [...state];
            newState.splice(index, 1);
            return newState;
        }
        default: {
            return state;
        }
    }
}
複製程式碼

reducer/index.js

import { combineReducers } from 'redux'
import list from './list';

const app = combineReducers({
    list,
})

export default app;
複製程式碼

store

store 就是我們當前專案的倉庫, 我們可以在 store儲存 我們專案中的用到的所有資料。 Redux 應用中只有一個單一的store。 如果需要對資料進行拆分, 可以把資料儲存在多個reducer中, 然後使用 combineReducers() 將多個reducer 合併為一個。

所有的容器元件都可以訪問 Redux Store, 一種方式是以 props 的形式傳入 容器元件中, 但這樣使用比較麻煩。 react 中 我們可以使用 , 這樣 我們所有的容器元件都可以訪問store,不需要通過 props 的方式進行傳遞。

在react中, 容器組減可以使用 connect() 方法訪問 store。使用 connect() 時, 使用mapStateToProps這個函式來指定當前 Redux store state 中需要對映到展示元件的 props 中的資料, 使用mapDispatchToProps() 方法接收 dispatch() 方法並返回期望注入到展示元件的 props 中的回撥方法。

import { connect } from 'react-redux';
import { addTodo } from 'action';

class Todo extends Component {
    // ...
}

export default connect(
  (state) => {
      return {
          list: state.list,
      }
  },
  {
    addTodo,
  }
)(Todo)
複製程式碼

redux 除錯工具

redux有很多 第三方除錯工具, 我們這裡使用redux-devtools-extension.

使用步驟:

  1. 首先,在Chrome中安裝Redux Devtools擴充套件。
  2. 用npm或yarn安裝redux-devtools-extension包。

index.js

// ...
import { composeWithDevTools } from 'redux-devtools-extension';

const store = createStore(app, composeWithDevTools());
複製程式碼

程式碼

專案結構

├── README.md ├── package.json ├── src │   ├── App.css │   ├── App.js │   ├── App.test.js │   ├── actions │   │   └── index.js │   ├── components │   │   ├── Footer │   │   │   ├── index.js │   │   │   └── style.css │   │   └── Todo │   │   ├── index.js │   │   └── style.css │   ├── index.css │   ├── index.js │   ├── logo.svg │   ├── reducer │   │   ├── index.js │   │   └── list.js │   └── registerServiceWorker.js └── yarn.lock

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import app from './reducer';
import registerServiceWorker from './registerServiceWorker';
import App from './App';
import './index.css';

const store = createStore(app, composeWithDevTools());

ReactDOM.render(
    <Provider store={store}>
    <App />
  </Provider>
, document.getElementById('root'));
registerServiceWorker();

複製程式碼

App.js

import React, { Component } from 'react';
import Footer from './components/Footer';
import Todo from './components/Todo';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <div className="App-intro">
          <Footer />
          <Todo />
        </div>
      </div>
    );
  }
}

export default App;

複製程式碼

action/index.js

export const ADD_TODO = 'ADD_TODO';
export const DEL_TODO = 'DEL_TODO';

export function addTodo(text) {
    return {
        type: ADD_TODO,
        text
    }
}

export function delTodo(index) {
    return {
        type: DEL_TODO,
        index
    }
}

複製程式碼

reducer/index.js

import { combineReducers } from 'redux'
import list from './list';

const app = combineReducers({
    list,
})

export default app;

複製程式碼

reducer/list.js

import {
    ADD_TODO,
    DEL_TODO,
} from '../actions';

export default function list(state = [], action) {
    switch (action.type) {
        case ADD_TODO: {
            const { text } = action;
            return [...state, text];
        }
        case DEL_TODO: {
            const { index } = action;
            let newState = [...state];
            newState.splice(index, 1);
            return newState;
        }
        default: {
            return state;
        }
    }
}
複製程式碼

component/Footer/index.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Input, message } from 'antd';
import { addTodo } from '../../actions';
import "./style.css";

const Search = Input.Search;

class Footer extends Component {
    constructor(props) {
        super(props);
        this.state = {
            inputValue: '',
        }
    }

    handleChange = (e) => {
        const inputValue = e.target.value;
        this.setState({
            inputValue
        })
    }

    addTodo = () => {
        const { inputValue } = this.state;
        if (String(inputValue)) {
            this.props.addTodo(inputValue);
            this.setState({
                inputValue: '',
            })
        } else {
            message.error('todo is require');
        }
    }
 
    render() {
        const { inputValue } = this.state;
        return (
            <div className="footer">
                <Search
                    value={inputValue}
                    placeholder="add a todo"
                    onChange={this.handleChange}
                    onSearch={this.addTodo}
                    enterButton="ADD"
                />
            </div>
        );
    }
}

export default connect(
    () => { return {} },{
        addTodo
    }
  )(Footer);

複製程式碼

component/Todo/index.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Button } from 'antd';
import { delTodo } from '../../actions';
import "./style.css";

class Todo extends Component {
    constructor(props) {
        super(props);
    }

    delTodo = (index) => {
        this.props.delTodo(index);
    }
 
    render() {
        const { list } = this.props;

        if (!list || !list.length) {
            return null;
        }
        return (
            <div className="todo">
                {
                    list.map((item, index) => {
                        return (
                            <div key={index} className="todoItem">
                                {item}
                                <Button onClick={() => this.delTodo(index)}>Del</Button>
                            </div>
                        )
                    })
                }
            </div>
        );
    }
}

export default connect(
    (state) => { return {
        list: state.list,
    } },{
        delTodo
    }
  )(Todo);

複製程式碼

相關文章