前端(十):使用redux管理數據

  react自己可以完成動態數據的監聽和更新,若是不是必要能夠不適用redux。css

  安裝redux: cnpm install redux --save,或者yarn add redux。
java

1、react基本用法

  redux是獨立的用於狀態管理的第三方包,它創建狀態機來對單項數據進行管理。node

  上圖是我的粗淺的理解。用代碼驗證一下:react

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux";

function reducer(state={name: "monkey", age: 5000}, action){
switch (action.type){
case "add":
state.age ++;
return state;
case "dec":
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
const store = createStore(reducer);
const add = {type: "add"}, dec={type: "dec"};


class App extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
const store = this.props.store;
// console.log(store.getState());
const obj = store.getState();
// console.log(obj);
return (
<div>
<h2>this { obj.name } is { obj.age } years old.</h2>
<button style={style} onClick={()=>store.dispatch(this.props.add)}>增長一歲</button>
<button style={style} onClick={()=>store.dispatch(this.props.dec)}>減小一歲</button>
</div>
)
}
}
function render() {
ReactDOM.render(<App store={store} add={ add } dec={ dec } />, document.getElementById('root'));
}
render();
store.subscribe(render);

  由於action必須是個對象,因此只能寫成add = {type: "add"}的形式,而不能直接寫參數"add"。一樣地,在reducer中寫switch時將action.type做爲參數。ios

  action和state一一對應,要使用action必需要在reducer裏聲明。npm

  redux沒有用state來實現動態數據更新,而是經過props來傳遞數據,所以在組件內部只能經過props獲取store,以及store.getState()獲取state。json

  redux將ReactDOM.render進行了一次封裝來設置監聽。redux

  redux對數據和組件進行了解耦,於是能夠進行文件拆分。axios

  把action寫成函數的形式:api

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux";

const Add = "add", Dec="dec";
function reducer(state={name: "monkey", age: 5000}, action){
switch (action.type){
case Add:
state.age ++;
if (state.age > 5005){
state.name = "old monkey";
}
return state;
case Dec:
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
const store = createStore(reducer);
class App extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
const store = this.props.store;
const state = store.getState();
return (
<div>
<h2>this { state.name } is { state.age } years old.</h2>
<button style={style} onClick={()=>store.dispatch(add())}>增長一歲</button>
<button style={style} onClick={()=>store.dispatch(dec())}>減小一歲</button>
</div>
)
}
}
function render() {
ReactDOM.render(<App store={store} add={ add } dec={ dec } />, document.getElementById('root'));
}
render();
store.subscribe(render);

   或者也能夠寫成原始的:

// reducer.js
const defaultState = {
    inputValue: '',
    list: [1, 2, 3]
};

// reducer能夠接受state,可是沒法修改state,因此只能拷貝修改
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 === 'add_item'){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        return newState;
    }
    if(action.type === 'delete_item'){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.value, 1);
        return newState;
    }
    return state;
}
// store.js
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );

export default store;
// ReduxDemo.js
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';
import store from './store';

class ReduxDemo extends React.Component{

    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleInpChange = this.handleInpChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
        // 使用subscribe監聽函數時必定要事先綁定到this上
        store.subscribe(this.handleStoreChange);
    }

    render(){
        return (
            <Fragment>
                <Row gutter={24} size="large">
                    <Col span={12} offset={4} style={{padding: "0px"}}>
                        <Input defaultValue='todo info' style={{height:"50px"}} value={this.state.inputValue}
                               onChange={this.handleInpChange}
                        />
                    </Col>
                    <Col span={2} style={{padding:"0px"}}>
                        <Button
                            type='primary'
                            style={{width:"100%", height:"50px"}}
                            onClick={this.handleBtnClick}
                        >submit</Button>
                    </Col>
                </Row>
                <List
                    bordered={1}
                    dataSource={this.state.list}
                    renderItem={
                        (item, index) => (
                            <List.Item column={12}
                                       onClick={this.handleItemDelete.bind(this,index)}
                            >{item}</List.Item>
                        )
                    }
                />
            </Fragment>
        )
    }
    handleBtnClick(){
        const action = {
            type: 'add_item'
        };
        store.dispatch(action);
    }

    handleInpChange(e) {
        const action = {
            type: 'change_input_value',
            value: e.target.value
        };
        store.dispatch(action);
    }
    handleStoreChange(){
        this.setState(()=>store.getState());
    };
    handleItemDelete(index){
        const action = {
            type: 'delete_item',
            value: index
        };
        store.dispatch(action);     // dispatch被監聽函數調用,並將action提交給store,store將上一次的數據狀態state和本次的action一塊兒交給reducer去執行邏輯處理
    }
}
export default ReduxDemo;

  或者改成:

// actioncreators
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';

export const getInpChangeAction = (value)=>({
        type: CHANGE_INPUT_VALUE,
        value
});
export const getAddItemAction = ()=>({
    type: ADD_ITEM,
});
export const getDeleteItemAction = (index)=>({
    type: DELETE_ITEM,
    value: index
});
// reducers
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM} from './actionCreators'

const defaultState = {
    inputValue: '',
    list: [1, 2, 3]
};

// reducer能夠接受state,可是沒法修改state,因此只能拷貝修改
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 === ADD_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        return newState;
    }
    if(action.type === DELETE_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.value, 1);
        return newState;
    }
    return state;
}
// store
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );

export default store;
// ReduxDemo
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';
import store from './store';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction } from './actionCreators';

class ReduxDemo extends React.Component{

    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleInpChange = this.handleInpChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
        // 使用subscribe監聽函數時必定要事先綁定到this上
        store.subscribe(this.handleStoreChange);
    }

    render(){
        return (
            <Fragment>
                <Row gutter={24} size="large">
                    <Col span={12} offset={4} style={{padding: "0px"}}>
                        <Input defaultValue='todo info' style={{height:"50px"}} value={this.state.inputValue}
                               onChange={this.handleInpChange}
                        />
                    </Col>
                    <Col span={2} style={{padding:"0px"}}>
                        <Button
                            type='primary'
                            style={{width:"100%", height:"50px"}}
                            onClick={this.handleBtnClick}
                        >submit</Button>
                    </Col>
                </Row>
                <List
                    bordered={1}
                    dataSource={this.state.list}
                    renderItem={
                        (item, index) => (
                            <List.Item column={12}
                                       onClick={this.handleItemDelete.bind(this,index)}
                            >{item}</List.Item>
                        )
                    }
                />

            </Fragment>
        )
    }
    handleBtnClick(){
        const action = getAddItemAction();
        store.dispatch(action);
    }
    handleInpChange(e) {
        const action = getInpChangeAction(e.target.value);
        store.dispatch(action);
    }
    handleStoreChange(){
        this.setState(()=>store.getState());
    };
    handleItemDelete(index){
        const action = getDeleteItemAction(index);
        store.dispatch(action);
    }
}
export default ReduxDemo;

2、UI組件與容器組件拆解

  容器組件負責邏輯處理,UI組件只負責頁面顯示。基於這樣的邏輯,能夠將上述的ReduxDemo進行拆解。

// 容器組件 import React from 'react'; import store from './store'; import ReduxDemoUI from './ReduxDemoUI'; import {getAddItemAction, getDeleteItemAction, getInpChangeAction } from './actionCreators'; class ReduxDemo extends React.Component{ constructor(props){ super(props); this.state = store.getState(); this.handleBtnClick = this.handleBtnClick.bind(this); this.handleInpChange = this.handleInpChange.bind(this); this.handleStoreChange = this.handleStoreChange.bind(this); this.handleItemDelete = this.handleItemDelete.bind(this); // 使用subscribe監聽函數時必定要事先綁定到this上 store.subscribe(this.handleStoreChange); } render(){ return ( <ReduxDemoUI inputValue={this.state.inputValue} list={this.state.list} handleBtnClick={this.handleBtnClick} handleInpChange={this.handleInpChange} handleStoreChange={this.handleStoreChange} handleItemDelete={this.handleItemDelete} />)  } handleBtnClick(){ const action = getAddItemAction(); store.dispatch(action); } handleInpChange(e) { const action = getInpChangeAction(e.target.value); store.dispatch(action); } handleStoreChange(){ this.setState(()=>store.getState()); }; handleItemDelete(index){ const action = getDeleteItemAction(index); store.dispatch(action); } } export default ReduxDemo;
// UI組件 import React, {Fragment} from 'react'; import 'antd/dist/antd.css'; import { Input, Button, Row, Col, List } from 'antd'; class ReduxDemoUI extends React.Component{ render(){ return ( <Fragment> <Row gutter={24} size="large"> <Col span={12} offset={4} style={{padding: "0px"}}> <Input defaultValue='todo info' style={{height:"50px"}} value={this.props.inputValue} onChange={this.props.handleInpChange} /> </Col> <Col span={2} style={{padding:"0px"}}> <Button type='primary' style={{width:"100%", height:"50px"}} onClick={this.props.handleBtnClick} >submit</Button> </Col> </Row> <List bordered={1} dataSource={this.props.list} renderItem={ (item, index) => ( <List.Item column={12} onClick={(index) => {this.props.handleItemDelete(index)}} >{item}</List.Item>  ) } /> </Fragment>  ) } } export default ReduxDemoUI;

   當一個組件只有render函數時,能夠改寫成無狀態組件,無狀態組件只是一個函數,所以性能要比UI組件高。

// 將ReduxDemoUI改寫
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';

export const ReduxDemoUI = (props) => {
    return (<Fragment>
        <Row gutter={24} size="large">
            <Col span={12} offset={4} style={{padding: "0px"}}>
                <Input defaultValue='todo info' style={{height: "50px"}} value={props.inputValue}
                       onChange={props.handleInpChange}
                />
            </Col>
            <Col span={2} style={{padding: "0px"}}>
                <Button
                    type='primary'
                    style={{width: "100%", height: "50px"}}
                    onClick={props.handleBtnClick}
                >submit</Button>
            </Col>
        </Row>
        <List
            bordered={1}
            dataSource={props.list}
            renderItem={
                (item, index) => (
                    <List.Item column={12}
                               onClick={() => {
                                   props.handleItemDelete(index)
                               }}
                    >{item}</List.Item>
                )
            }
        />
    </Fragment>)};
// ReduxDemo只修改導入方式
// import ReduxDemoUI from './ReduxDemoUI';
import {ReduxDemoUI} from './ReduxDemoUI';

3、請求數據渲染

  利用redux+axios發送數據,獲取數據並將數據渲染。

// actionCreators
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';

export const AXIO_LIST = 'axios_list'; // 添加狀態

export const getInpChangeAction = (value)=>({
        type: CHANGE_INPUT_VALUE,
        value
});
export const getAddItemAction = ()=>({
    type: ADD_ITEM,
});
export const getDeleteItemAction = (index)=>({
    type: DELETE_ITEM,
    value: index
});

// 添加action export const getAxiosListAction
= (list) => ({ type: AXIO_LIST, list });
// reducers
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, AXIO_LIST} from './actionCreators'

const defaultState = {
    inputValue: '',
    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 === ADD_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        return newState;
    }
    if(action.type === DELETE_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.value, 1);
        return newState;
    }
  // 添加狀態處理
if(action.type === AXIO_LIST){ const newState = JSON.parse(JSON.stringify(state)); newState.list = action.list; return newState; } return state; }
// ReduxDemo

import React from 'react';
import store from './store';
import axios from 'axios';
import {ReduxDemoUI} from './ReduxDemoUI';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction, getAxiosListAction } from './actionCreators';

class ReduxDemo extends React.Component{
    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleInpChange = this.handleInpChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
        // 使用subscribe監聽函數時必定要事先綁定到this上
        store.subscribe(this.handleStoreChange);
    }

    render(){
        return (
            <ReduxDemoUI
                inputValue={this.state.inputValue}
                list={this.state.list}
                handleBtnClick={this.handleBtnClick}
                handleInpChange={this.handleInpChange}
                handleStoreChange={this.handleStoreChange}
                handleItemDelete={this.handleItemDelete}
            />)
    }
    handleBtnClick(){
        const action = getAddItemAction();
        store.dispatch(action);
    }
    handleInpChange(e) {
        const action = getInpChangeAction(e.target.value);
        store.dispatch(action);
    }
    handleStoreChange(){
        this.setState(()=>store.getState());
    };
    handleItemDelete(index){
        const action = getDeleteItemAction(index);
        store.dispatch(action);
    }
   // 發送請求,獲取list,並交給store,store將action和list交給reducer處理,而後store在constructor中監聽返回
// 而後storec調用store.dispatch將reducer更新後的數據更新到組件的setState中,隨後render方法開始渲染 componentDidMount() { axios({ method:
'get', url: 'http://localhost:8080/ccts/node/test', responseType: 'json' }).then((response) => { // 請求成功時 const action = getAxiosListAction(response.data.list); store.dispatch(action); }).catch((error) => { console.log(error); }); } } export default ReduxDemo;

  store.js和ReduxDemoUI.js保持不變。啓動一個後臺服務,這裏用java起了一個後臺來傳遞list。注意在package.json中設置proxy爲"http://localhost:8080"。

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/node")
public class Service {
    @RequestMapping(value = {"/test"}, method = {RequestMethod.GET})
    @ResponseBody
    public String test(HttpServletRequest request){
        // consumes = "application/json")
        Map<String, ArrayList> map = new HashMap<String, ArrayList>(1);
        ArrayList<String> strings = new ArrayList<String>();
        strings.add("張三");
        strings.add("李四");
        strings.add("王二");
        strings.add("麻子");
        map.put("list", strings);
        return JSON.toJSONString(map);
    }
}

4、redux異步處理

  當一個父組件中有多個子組件時,若是每個組件發生狀態變化,store都要從新渲染一遍。使用異步能夠只從新渲染髮生狀態變化的組件。

  安裝redux和thunk的中間件redux-thunk:cnpm install redux-thunk --save。引入兩個函數,並修改createStore以下,其他代碼保持不變:

import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
const store = createStore(reducer, applyMiddleware(thunk));

  固然還可使用compose在控制檯添加調試功能,前提是這須要在瀏覽器中安裝redux插件,百度下載redux-devtools.crx,並直接拖動到谷歌瀏覽器擴展程序頁面(這就是安裝了)。

import { createStore, applyMiddleware, compose } from "redux";
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
window.devToolsExtension?window.devToolsExtension():f=>f
)
);

  最終代碼以下:

import {createStore, applyMiddleware, compose} from 'redux';
import thunk from  'redux-thunk';
import reducer from './reducer';
const store = createStore(
    reducer,
    compose(
        applyMiddleware(thunk),
        window.devToolsExtension?window.devToolsExtension():f=>f
    )
);
export default store;

  thunk支持異步請求,它的action能夠是一個函數。爲了啓用異步請求,對請求進行改寫。

      thunk中間件指的是store和action之間的中間件。當store.dispatch被執行時,會檢測action是一個對象或是一個函數,當是一個函數時,會自動執行這個函數並將結果再交給store。而後和對象那種處理方式同樣,store將action交給reducer處理。

// actionCreators添加以下方法
export const getAxiosListThunk = () => {
    // 返回一個函數,它能夠自動接收一個名爲dispatch的方法,處理異步
    return (dispatch) => {
        axios({
            method: 'get',
            url: 'http://localhost:8080/ccts/node/test',
            responseType: 'json'
        }).then((response) => {
            // 請求成功時
            const action = getAxiosListAction(response.data.list);
            dispatch(action); // 只將store.dispatch()改寫成了dispatch()
        }).catch((error) => {
            console.log(error);
        });
    };
};
// ReduxDemo中修改componnetDidMount
    componentDidMount() {
        const action = getAxiosListThunk();
        store.dispatch(action); // dispatch會自動調用和執行action函數
        // axios({
        //     method: 'get',
        //     url: 'http://localhost:8080/ccts/node/test',
        //     responseType: 'json'
        // }).then((response) => {
        //     // 請求成功時
        //     const action = getAxiosListAction(response.data.list);
        //     store.dispatch(action);
        // }).catch((error) => {
        //     console.log(error);
        // });
    }

5、組件間與狀態機、狀態機與狀態機

  內層的組件調用dispatch,那麼它的父組件一直到最外層組件都須要使用store屬性一層一層地傳遞狀態機。

  狀態機與狀態機之間須要使用redux中的combineReducers合併成一個對象。並由dispatch調用的action來查找其對應的reducer一級返回的state。

  舉個例子:

  在src目錄下新建index.redux.js文件,創建兩個reducer以及各自的action。

// src/index.redux.js
const Add = "add", Dec="dec";
export function reducerOne(state={name: "monkey", age: 5000}, action){
switch (action.type){
case Add:
state.age ++;
if (state.age > 5005){
state.name = "old monkey";
}
return state;
case Dec:
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
export function add() {
return {type: Add}
}
export function dec() {
return {type: Dec}
}

export function reducerTwo(state="孫悟空", action){
if(action.type >= 5005){
state = "鬥打敗佛";
}else if(action.type <= 4995){
state = "小獼猴";
}
return state;
}
export function monkey(number) {
return {type: number}
}

  在src/index.js中寫入以下代碼:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from 'redux-thunk';

import { reducerOne, reducerTwo, add, dec, monkey } from "./index.redux";

const reducer = combineReducers({reducerOne, reducerTwo});
const store = createStore(reducer, applyMiddleware(thunk));

class Nick extends React.Component{
render(){
const store = this.props.store;
const state = store.getState();
return <h2 >Now this monkey is so-called { state.reducerTwo }</h2>
}
}
class Monkey extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
const store = this.props.store;
const state = store.getState();
const name = state.reducerOne.name, age = state.reducerOne.age;
return (
<div>
<h2>this { name } is { age } years old.</h2>
<button style={style} onClick={()=>{store.dispatch(add()); store.dispatch(monkey(age))}}>增長一歲</button>
<button style={style} onClick={()=>{store.dispatch(dec()); store.dispatch(monkey(age))}}>減小一歲</button>
<Nick store={store} />
</div>
)
}
}
function render() {
ReactDOM.render(<Monkey store={store} add={ add } dec={ dec } />, document.getElementById('root'));
}
render();
store.subscribe(render);

  在Monkey組件中,onClick同時觸發兩個狀態機reducerOne,reducerTwo。Nick組件接收點擊事件觸發的狀態修改以後的store,並從中獲取相應的字段進行渲染。在這裏須要注意:

  1.使用combineReducers將Monkey複合組件涉及到的狀態機進行合併。

  2.多個reducer時,store.getState()獲取的是一個對象。能夠經過reducer名來獲取對應reducer的state。

  3.store和action必須一級一級的往下傳遞。

6、使用react-redux(以Monkey和Nick爲例)

   react-redux簡化了redux在組件的依次傳遞問題。

  react-redux有兩個核心api:Provider和connect。Provider聲明全部被其包裹的子組件均可以從其store屬性中獲取數據,而沒必要逐次傳遞。connect容許每一個組件跟Provider建立鏈接來獲取數據。

  這裏對上面的代碼進行修改:

  - src/index.redux.js文件內容以下:reducer中的state此時必須從新定義,不能直接再返回原來的state。而後微調了一下顯示的數據。

// src/index.redux.js
const Add = "add", Dec="dec";
export function reducerOne(state={name: "monkey", age: 5000}, action){
switch (action.type){
case Add:
if (state.age >= 5004){
state.name = "old monkey";
}else if(state.age > 4994 && state.age < 5004){
state.name = "monkey";
}
return {...state, age: state.age+1};
case Dec:
if (state.age <= 4996){
state.name = "small monkey";
}else if(state.age > 4996 && state.age <= 5005){
state.name = "monkey";
}
return {...state, age: state.age-1};
default:
return state;
}
}
export function add() {
return {type: Add}
}
export function dec() {
return {type: Dec}
}
export function reducerTwo(state="孫悟空", action){
if(action.type >= 5004){
return "鬥打敗佛";
}else if(action.type <= 4996){
return "小獼猴";
}
return state;
}
export function monkey(number) {
return {type: number}
}

  - src/index.js內容以下:

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from 'redux-thunk';
import { Provider, connect } from 'react-redux';

import { reducerOne, reducerTwo, add, dec, monkey } from "./index.redux";

const reducer = combineReducers({reducerOne, reducerTwo});
const store = createStore(reducer, applyMiddleware(thunk));

class Nick extends React.Component{
render(){
return <h2 >Now this monkey is so-called { this.props.nick }</h2>
}
}
class Monkey extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
return (
<div>
<h2>this { this.props.reducerOne.name } is { this.props.reducerOne.age } years old.</h2>
<button style={style} onClick={()=>{this.props.add(); this.props.monkey(this.props.reducerOne.age)}}>增長一歲</button>
<button style={style} onClick={()=>{this.props.dec();this.props.monkey(this.props.reducerOne.age)}}>減小一歲</button>
<Nick nick={ this.props.reducerTwo } />
</div>
)
}
}
const mapStateProps = state=>{
return { reducerOne: state.reducerOne, reducerTwo: state.reducerTwo }
};
const actionCreator = { add, dec, monkey };
Monkey = connect(mapStateProps, actionCreator)(Monkey); // 實際上是加了一層裝飾器


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

  首先,從react-redux中引入Provider,用來包裝app Monkey,而後將store傳遞給Provider便可。

  其次,用裝飾器對app Monkey進行裝飾,並傳遞進去兩個參數,第一個參數是組件所須要的reducer中的狀態(這裏把兩個reducer的狀態所有寫了進來),第二個參數是action指令。connect函數會根據將reducer和action一一對應起來,因此後面直接用action函數便可。

  再次,onClick綁定action只須要寫成onClick={this.props.action}便可,這裏須要變動兩個狀態,因此用箭頭函數包裹了一下。

  connect的這種寫法看起來不爽,能夠用裝飾器的形式寫。可是須要作一下babel配置:

# 1.生成配置文件
cnpm run eject
# 2.安裝插件
cnpm install babel-plugin-transform-decorators-legacy --save
# 3.在package.json中的"babel"中作以下配置:
src/package.json
"babel": {
"presets": [
"react-app"
],
"plugins":[
"transform-decorators-legacy"
]
},

  此時將裝飾器的那三行代碼改成一行,寫在Monkey類上面便可。

@connect(state=>({ reducerOne: state.reducerOne, reducerTwo: state.reducerTwo }), { add, dec, monkey })
class Monkey extends React.Component{
  ...
}

  備註:react-16.6.3,babel-7.0以上安裝須要作以下配置。

1.npm run eject
2.安裝插件
cnpm install babel-plugin-transform-decorators-legacy --save
cnpm install --save-dev babel-preset-env
cnpm install --save-dev babel-plugin-transform-class-properties
cnpm install --save-dev @babel/plugin-transform-react-jsx
cnpm install --save-dev @babel/plugin-transform-react-jsx-source
cnpm install --save-dev @babel/plugin-transform-react-jsx-self
cnpm install --save-dev @babel/plugin-proposal-decorators
3.修改package.json
"babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      [
        "@babel/plugin-proposal-decorators",
        {
          "legacy": true
        }
      ]
    ]
}

 

7、使用React-Redux(以ReduxDemo爲例)

  保持reducers.js、store.js、actionCreators.js不變,改寫ReduxDemo.js和index.js,刪除ReduxDemoUI.js。

// reducers.js
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, AXIO_LIST} from './actionCreators'

const defaultState = {
    inputValue: '',
    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 === ADD_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        return newState;
    }
    if(action.type === DELETE_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.value, 1);
        return newState;
    }
    if(action.type === AXIO_LIST){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list = action.list;
        return newState;
    }
    return state;
}
// store.js
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from  'redux-thunk';
import reducer from './reducer';
const store = createStore(
    reducer,
    compose(
        applyMiddleware(thunk),
        window.devToolsExtension?window.devToolsExtension():f=>f
    )
);
export default store;
import axios from "axios";

export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';

export const AXIO_LIST = 'axios_list';

export const getInpChangeAction = (value)=>({
        type: CHANGE_INPUT_VALUE,
        value
});
export const getAddItemAction = ()=>({
    type: ADD_ITEM,
});
export const getDeleteItemAction = (index)=>({
    type: DELETE_ITEM,
    value: index
});

export const getAxiosListAction = (list) => ({
    type: AXIO_LIST,
    list
});

export const getAxiosListThunk = () => {
    // 返回一個函數,它能夠自動接收一個名爲dispatch的方法,處理異步
    return (dispatch) => {
        axios({
            method: 'get',
            url: 'http://localhost:8080/ccts/node/test',
            responseType: 'json'
        }).then((response) => {
            // 請求成功時
            const action = getAxiosListAction(response.data.list);
            dispatch(action);
        }).catch((error) => {
            console.log(error);
        });
    };
};
// ReduxDemo.js
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';
import { connect } from 'react-redux';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction, getAxiosListThunk } from './actionCreators';

class ReduxDemo extends React.Component{
    render(){
        return (
            <Fragment>
                <Row gutter={24} size="large">
                    <Col span={12} offset={4} style={{padding: "0px"}}>
                        <Input defaultValue='todo info' style={{height: "50px"}} value={this.props.inputValue}
                               onChange={(e) => {this.props.getInpChangeAction(e.target.value)}}
                        />
                    </Col>
                    <Col span={2} style={{padding: "0px"}}>
                        <Button
                            type='primary'
                            style={{width: "100%", height: "50px"}}
                            onClick={() => {this.props.getAddItemAction()}}
                        >submit</Button>
                    </Col>
                </Row>
                <List
                    bordered={1}
                    dataSource={this.props.list}
                    renderItem={
                        (item, index) => (
                            <List.Item column={12}
                                       onClick={() => {
                                           this.props.getDeleteItemAction(index)
                                       }}
                            >{item}</List.Item>
                        )
                    }
                />
            </Fragment>)
    }
    componentDidMount() {
        this.props.getAxiosListThunk();
    }
}

// 接收一個固定的state參數,並返回一個對象
// connect -> props和store作鏈接,將store中的數據映射到props屬性中去
const mapStateProps = (state) => {
    return { inputValue: state.inputValue, list: state.list }
};
// 一樣地,將store中的action映射到props屬性中去
const mapActionProps = {
        getAddItemAction,
        getDeleteItemAction,
        getInpChangeAction,
        getAxiosListThunk
};

ReduxDemo = connect(mapStateProps, mapActionProps)(ReduxDemo);
export default ReduxDemo;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';

import store from './Boom09/store';
import ReduxDemo from './Boom09/ReduxDemo';

const App = (
    <Provider store={store}>
        <ReduxDemo />
    </Provider>
);

ReactDOM.render(App, document.getElementById('root'));

 

 

4、react-router-dom

  react-router-dom是專門用於處理路由的組件。

  BrowserRouter用以包裹app,它會在this.props添加一些環境參數,這很是用。

  Route用於將路由和組件綁定,一個路由對應一個頁面(組件),或者Link。其重要的兩個屬性爲path和component,path能夠接收對象,也能夠接收一個url字符串。

  Link用於將連接和Route進行綁定。

  Switch用於當前路由下的全部子路由,通常和Redirect配合使用,用於重定向那些未定義的子路由。

  Redirect:重定向。其重要的屬性爲to,只能指向一個url字符串。

  用例一: Route + Componnet + Redirect

  1.創建兩個路由指向兩個組件:Login和Home, 以及一個用戶登陸狀態的reducer。

  2.進入時首先重定向到Home頁面.Home組件判斷用戶登陸狀態,登陸則打印登陸信息並提供註銷按鈕,點擊按鈕會重定向到Login頁面;未登陸則直接重定向到Login頁面。

  3.用戶登陸則重定向到Home頁面,未登陸則打印登陸提示並提供登陸按鈕,點擊按鈕會重定向到Home頁面。

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import {BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';

import { reducer } from "./reducer";
import { Login } from "./Login";
import { Home } from "./Home";

const store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
(<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/login" component={ Login } />
<Route path="/monkey" component={ Home } />
<Redirect to={{ pathname: "/monkey" }}/>
</Switch>
</BrowserRouter>

</Provider>),
document.getElementById('root')
);
// src/Home.js
import React from "react";
import {connect} from 'react-redux';
import { Redirect } from 'react-router-dom';
import {logout} from "./reducer";

@connect(state=>state, {logout})
class Home extends React.Component{
render(){
const app = (
<div>
<h2>這是home界面</h2>
<button onClick={this.props.logout}>點擊註銷</button>
</div>
);
return this.props.isLogin ? app : <Redirect to={{ pathname: "/login"}} />
}
}
export { Home }
// src/Login.js
import React from 'react';
import {connect} from 'react-redux';
import {Redirect} from 'react-router-dom';
import {login} from "./reducer";

@connect(state=>state, {login})
class Login extends React.Component{
render(){
console.log(this.props);
const app = (
<div>
<h2>請先登陸!</h2>
<button onClick={this.props.login}>點擊登陸</button>
</div>
);
return this.props.isLogin ? <Redirect to={{pathname: "/home"}} /> : app
}
}
export { Login }
// src/reducer.js
// 用戶登陸狀態
const LOGIN = "login";
const LOGOUT = "logout";

export function reducer(state={isLogin: false, user: "Li"}, action) {
switch (action.type){
case LOGIN:
return {...state, isLogin:true};
case LOGOUT:
return {...state, isLogin:false};
default:
return state
}
}

export function login() {
return {type: LOGIN}
}
export function logout() {
return {type: LOGOUT}
}

  用例二:Route + Component + Link

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Route, Switch, Redirect, Link } from 'react-router-dom';


function LinkOne(){
return <h2>我是LineOne組件,被Route綁定給了一個Link連接</h2>
}
function LinkTwo() {
return <h2>我是LineThree組件,被Route綁定給了一個Link連接</h2>
}
function LinkThree(){
return <h2>我是LineThree組件,被Route綁定給了一個Link連接</h2>
}
function Test(){
return <h2>這是無效的路由,你不要搞事情!</h2>
}

ReactDOM.render(
(<BrowserRouter>
<div>
<ul>
<li><Link to="/">LinkOne</Link></li>
<li><Link to="/two">LinkTwo</Link></li>
<li><Link to="/three">LinkThree</Link></li>
</ul>
<Switch>
<Route path="/" exact component={ LinkOne } />
<Route path="/two" component={ LinkTwo } />
<Route path="/three" component={ LinkThree } />
<Route path='/:location' component={ Test } />
</Switch>
</div>
</BrowserRouter>),
document.getElementById('root')
);
相關文章
相關標籤/搜索