1. 程式人生 > >React (5) Redux 進階

React (5) Redux 進階

1.  UI 元件

  1. render()函式放在另外一個單獨的UI.js 檔案和動作行為分開
  2. UI 上面所用的資料和函式, 通過父元件傳遞過來,用 this.props 來接收
  3. UI 元件下面:
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import React, {Component} from 'react';

class TodoListUI extends Component {
    render(){
        return ( <div style={{ margin: '10px' }}>
        <div>
            <Input value={this.props.inputValue}
             style={{ width: '300px', marginRight: '10px' }}
             onChange={this.props.handleInputChange}
             />
            <Button type="primary"
            onClick={this.props.btnClick}
            >提交</Button>
        </div>
        <List
            bordered
            dataSource={this.props.list}
            renderItem={(item, index) => (<List.Item onClick={(index)=>{this.props.handledelete(index)}}>{item}</List.Item>)}
            style={{marginTop:'10px',width:'300px'}}
        />
    </div>)
    }
}

export default TodoListUI

父元件傳遞資料

 render() {
        return (
           <TodoListUI 
           inputValue={this.state.inputValue}
           handleInputChange={this.handleInputChange}
           btnClick={this.btnClick}
           list={this.state.list}
           handledelete={this.handledelete}
           />
        )
    }

 

2.  無狀態元件

  1. 當一個普通的元件只有render()函式的時候, 沒有業務邏輯的時候它就是一個無狀態元件, 類似沒有業務邏輯的UI元件
  2. 無狀態元件效能比較高, 因為它就是一個js, 一個普通元件既包含一個類還有render(), 效能差別就在這
  3. 下面是一個無狀態元件
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import React from 'react';

const TodoListUI = (props) => {
    return (
        <div style={{ margin: '10px' }}>
            <div>
                <Input value={props.inputValue}
                    style={{ width: '300px', marginRight: '10px' }}
                    onChange={props.handleInputChange}
                />
                <Button type="primary"
                    onClick={props.btnClick}
                >提交</Button>
            </div>
            <List
                bordered
                dataSource={props.list}
                renderItem={(item, index) => (<List.Item onClick={(index) => { props.handledelete(index) }}>{item}</List.Item>)}
                style={{ marginTop: '10px', width: '300px' }}
            />
        </div>
    )
}

export default TodoListUI

 

3.  Redux 傳送非同步請求 (使用redux-thunk)

  1. 使用 axios
  2. 使用 redux-thunk 將非同步函式寫在redux/動作檔案裡面, 不用redux-thunk的話可以將ajax 回撥放在元件的生命週期函式裡面

    但是,隨著元件的內容增多, 元件可能相對的比較複雜, 所以建議還是使用中介軟體

生命週期函式直接提交函式

 componentDidMount(){
       const action = getTodoList();
       store.dispatch(action); // 當呼叫這個函式時, 非同步函式會自動執行
    }

redux/action檔案函式

export const getTodoList = () => {
   return (dispatch) => {
    axios.get('/list.json').then((res)=>{
        const data = res.data;
        const action = initListAction(data);
        dispatch(action);
    })
   }
};

store檔案

import {
    createStore,
    applyMiddleware,
    compose
} from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';

const composeEnhancers =
  typeof window === 'object' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
      // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
    }) : compose;

const enhancer = composeEnhancers(
    applyMiddleware(thunk),
    // other store enhancers if any
);

const store = createStore(reducer,enhancer); 

export default store;

ps : 如果不適用 redux-thunk, 直接在生命週期函式中使用(但是不建議)

 componentDidMount(){
        axios.get('/list.json').then((res)=>{
        const data = res.data;
        const action = initListAction(data);
        dispatch(action);
    })
    }

 

4.  Redux 中介軟體

一般情況 : 在action通過 dispatch這個方法, 把物件傳送給store

使用中介軟體: 中介軟體簡單來說就是, 對store的dispatch方法進行升級, 允許傳過來一個函式,而不是隻能是物件. 在傳過來的時候, 對傳過來的action進行判斷, 如果是函式,則先執行函式,再進行下一步的操作, 就好像這樣,傳遞過來的函式立即執行, 這個函式預設引數是dispatch , 然後通過 dispatch 提交下一個action動作

使用的是redux-thunk中介軟體

export const getTodoList = () => {
   return (dispatch) => {
    axios.get('/list.json').then((res)=>{
        const data = res.data;
        const action = initListAction(data);
        dispatch(action);
    })
   }
};

ps : 只有redux才有中介軟體, 而不是react

5.  Redux-saga 的基本使用

1.  store/ index 下配置 (github上面有詳細的配置)

import {
    createStore,
    applyMiddleware,
    compose
} from 'redux';
import createSagaMiddleware from 'redux-saga'
import reducer from './reducer';
import mySaga from './sagas'

const sagaMiddleware = createSagaMiddleware();
const composeEnhancers =
  typeof window === 'object' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

const enhancer = composeEnhancers(
    applyMiddleware(sagaMiddleware)
);

const store = createStore(reducer,enhancer); 
sagaMiddleware.run(mySaga);
export default store;

2.actionCreators.js 建立行為型別

export const getInitList = () => ({
    type: GET_INIT_LIST,
});

3.  執行步驟, 元件周期函式派發action

 componentDidMount(){
        const action = getInitList();
        store.dispatch(action)
}

4.sagas.js 接收action判斷行為型別 (使用方法詳見github)  

   saga就是元件派發action後, 攔截下來, 優先處理,該型別的函式,

   但是與thunk不同的是, saga內建了一些方法可以處理非同步函式

import {takeEvery, put} from 'redux-saga/effects';
import {GET_INIT_LIST} from './actionTypes';
import {initListAction} from './actionCreators';
import axios from 'axios';

function* getInitList() {
    try{ // 成功的時候
        const res = yield axios.get('/list.json');
        const action = initListAction(res.data);
        yield put(action)
    }catch(e){
        console.log('list.json 網路請求失敗')
    }  
}


// generator 函式
function* mySaga() {
    yield takeEvery(GET_INIT_LIST, getInitList); // 判斷action型別,處理
}

export default mySaga;

5.saga 內建 put 方法繼續提交action給reducer執行後面的步驟, 後面的就是redux常規的步驟

 

6.  React-Redux

1.  檔案格式

2. index.js 入口檔案

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
import {Provider} from 'react-redux';
import store from './store';

// 這個標籤裡面的所有元件都可以使用store裡面的資料
// Provider 這元件可以把store提供給它含的所有元件
const App=(
    <Provider store={store}> 
        <TodoList/>
    </Provider>
)

// JSX 語法
ReactDOM.render(App, document.getElementById('root'));

3.  元件

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

const TodoList =(props) => { // 無狀態元件, 效能更佳
    
        const {inputValue,list,changeInputValue,putChange,delClick} = props
        return (                                                                                               
            
            <div>
                <div>
                    <input value={inputValue} onChange={changeInputValue} />
                    <button onClick={putChange}>提交</button>
                </div>
                <ul>
                    {list.map((item, index) => {
                       return <li key={index} onClick={delClick.bind(this, index)}>{item}</li>
                    })}
                    
                </ul>
            </div>
        )
    
}

// mapStateToProps 箭頭函式裡面的state是和store裡面的state對應的
const mapStateToProps = (state) => {
    return {
        inputValue: state.inputValue,
        list:state.list
    }
}

// mapDispatchToProps 箭頭函式裡面的state是和store裡面的state對應的
// store.dispatch, props
const mapDispatchToProps = (dispatch) => {
    return {
        changeInputValue(e) {
            const action = {
                type: 'change_input_value',
                value: e.target.value
            }
            dispatch(action);
        },
        putChange() {
            const action = {
                type: 'put_change'
            }
            dispatch(action)
        },
        delClick(index){
            const action = {
                type: 'del_click',
                index: index
            }
            dispatch(action)
        }
    }
}


// connect(null, null) (TodoList)
// 這個語法的意思是, 和store連結
// 第一個引數是state對映
// 第二個引數是action對映
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

4. store/index.js

import {createStore} from 'redux';
import reducer from './reducer';

const store = createStore(reducer);

export default store;

5.  store/reducer.js

const defaultState ={
    inputValue : 'hello',
    list : []
}

export default (state=defaultState,action) =>{
    if(action.type === 'change_input_value'){
        const newState = JSON.parse(JSON.stringify(state)); // 深拷貝
        newState.inputValue = action.value;
        return newState;
    }
    if(action.type==='put_change'){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        newState.inputValue = '';
        return newState;
    }
    if(action.type ==='del_click'){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.index,1);
        return newState;
    }
    return state;
}