Redux 一步到位

簡介

  • Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理
  • Redux 除了和 React 一塊兒用外,還支持其它庫( jquery ... )
  • 它體小精悍(只有2kB,包括依賴)
  • 由 Flux 演變而來,但受 Elm 的啓發,避開了 Flux 的複雜性。

安裝

  • 穩定版 npm install --save redux
  • 附加包 React 綁定庫 npm install --save react-redux
  • 附加包 開發者工具 npm install --save-dev redux-devtools

建立 reducer.js

應用中全部的 state 都以一個對象樹的形式儲存在一個單一的 store 中。 唯一改變 state 的辦法是觸發 action,一個描述發生什麼的對象。 爲了描述 action 如何改變 state 樹,先編寫 reducers。react

const defaultState = {}
export default (state = defaultState,action)=>{
    return state
}

建立 Store

import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store
  • 使用 createStore 建立數據儲存倉庫
  • 將 store 暴露出去

獲取 store

組件來獲取 store 中的數據jquery

import store from './store'
// ...
constructor(props){
    super(props)
    console.log(store.getState())
}
  • 先引入store
  • 使用 getState 函數獲取數據

安裝 Redux DevTools

chrome 搜索插件 Redux DevTools 並安裝ios

import { createStore } from 'redux'
 import reducer from './reducer'
 const store = createStore(reducer,
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
 export default store
  • 只是添加了一句話
  • 意思是看window裏有沒有這個方法,有則執行這個方法
  • 啓動項目就能夠看到 State 了

Action

Action 是 store 數據的惟一來源。chrome

建立 actionnpm

const action ={
        type:'',
        value: ''
    }

store.dispatch(action)
  • type 字段來表示將要執行的動做(必需要有)
  • 除了 type 字段外,action 對象的結構徹底自由
  • 使用 dispatch 函數發送數據到 store

更改 Reducerredux

export default (state = defaultState,action)=>{
    if(action.type === 'changeInput'){
        let newState = JSON.parse(JSON.stringify(state)) //深度拷貝state
        newState.inputValue = action.value
        return newState
    }
    return state
}
  • 先判斷type是否正確,若是正確,聲明一個變量newState
  • Reducer 裏只能接收 state,不能改變 state,因此將新變量 return

更新組件數據axios

constructor(props){
    // ...
    storeChange(){
     this.setState(store.getState())
    }
    this.storeChange = this.storeChange.bind(this)
    store.subscribe(this.storeChange)
}
  • bing(this) 轉變this指向
  • storeChange 從新setState
  • subscribe 函數用來訂閱 store 狀態

小技巧

抽離 Action Types

使用單獨的模塊或文件來定義 action type 常量並非必須的,甚至根本不須要定義。對於小應用來講,使用字符串作 action type 更方便些。不過,在大型應用中把它們顯式地定義成常量仍是利大於弊的。app

actionTypes.jsdom

const ADD_TODO = 'ADD_TODO';
const REMOVE_TODO = 'REMOVE_TODO';
const LOAD_ARTICLE = 'LOAD_ARTICLE';

組件中引用異步

import { ADD_TODO , REMOVE_TODO , LOAD_ARTICLE } from './store/actionTypes'

相應的 Reducer 也要更改

import { ADD_TODO , REMOVE_TODO , LOAD_ARTICLE } from './store/actionTypes'

const defaultState = {}
export default (state = defaultState,action)=>{
    if(action.type === ADD_TODO){
        let newState = JSON.parse(JSON.stringify(state))
        newState.inputValue = action.value
        return newState
    }
    // ...
    return state
}

抽離 Redux Action

Action 建立函數 就是生成 action 的方法。注意與 action 概念相區分。

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

actionCreators.js

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

組件中使用

import { addTodo } from './actionCreators';

// ...
dispatch(addTodo('Use Redux'))

注意

  • store 必須是惟一的
  • 只有store能改變本身的內容,Reducer不能改變
  • Reducer必須是純函數

拆分組件UI和業務邏輯

TodoListUI.js

import React, { Component } from 'react';
class TodoListUi extends Component {

    render() {
        return ( <div>123</div> );
    }
}

export default TodoListUi;

TodoList.js

import TodoListUI from './TodoListUI'

render() {
    return (
        <TodoListUI />
    );
}
  • constructor 中對於對應方法要從新綁定 this
  • 修改完 TodoList.js 文件,還要對UI組件進行對應的屬性替換

無狀態組件

  • 無狀態組件其實就是一個函數
  • 不用繼承任何的 class
  • 不存在 state
  • 由於無狀態組件其實就是一個函數, 性能比普通的React組件好

TodoListUi 改寫成無狀態組件

import React from 'react';

const TodoListUi = (props)=>{
    return(
        <> some code </>
    )
}

export default TodoListUi;

Axios 異步獲取數據和 Redux 結合

不過就是走一遍上面的流程

actionCreatores.js

export const getListAction  = (data)=>({
    type: xxx,
    data
})

組件

import axios from 'axios'
import {getListAction} from './store/actionCreatores'

componentDidMount(){
    axios.get('https:// xxx').then((res)=>{
        const data = res.data
        const action = getListAction(data)
        store.dispatch(action)
    })
}

reducer.js

import {GET_LIST} from './actionTypes'

const defaultState = {
    list:[]
}
export default (state = defaultState,action)=>{
    if(action.type === GET_LIST ){
        let newState = JSON.parse(JSON.stringify(state))
        newState.list = action.data.data.list
        return newState
    }

    return state
}

Redux 中間件

注意不是 react 中間件

Redux-thunk

  • Redux-thunk
  • Redux-thunk 是對 Redux 中 dispatch 的增強
npm install --save redux-thunk
import { createStore , applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const store = createStore(
    reducer,
    applyMiddleware(thunk)
)
  • 使用中間件須要先引入 applyMiddleware
  • 能夠這樣 可是咱們使用 Dev Tool 佔用了第二個參數

因此咱們這樣寫

import { createStore , applyMiddleware ,compose } from 'redux'

const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose
const enhancer = composeEnhancers(applyMiddleware(thunk))
const store = createStore( reducer, enhancer)
export default store
  • 利用compose創造一個加強函數 composeEnhancers,就至關於創建了一個鏈式函數
  • 把thunk加入 ( applyMiddleware(thunk) )
  • 直接在createStore函數中的第二個參數,使用這個 enhancer 變量

在 actionCreators.js 中寫業務

actionCreators.js 都是定義好的 action,根本沒辦法寫業務邏輯,有了Redux-thunk以後,能夠把TodoList.js中的 componentDidMount 業務邏輯放到這裏來編寫。

import axios from 'axios'

//...
export const getTodoList = () =>{
    return (dispatch)=>{
        axios.get('https:// xxx ').then((res)=>{
            const data = res.data
            const action = getListAction(data)
            dispatch(action)
        })
    }
}

之前的action是對象,如今的action能夠是函數了,這就是redux-thunk帶來的好處

組件中

import { getTodoList } from './store/actionCreatores'
// ...
componentDidMount(){
    const action = getTodoList()
    store.dispatch(action)
}

Redu-saga

安裝

npm install --save redux-saga

store/index.js

import createSagaMiddleware from 'redux-saga'
const sagaMiddleware = createSagaMiddleware();
  • 引入saga
  • 建立saga中間件

Redux-thunk 替換成 saga

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

const sagaMiddleware = createSagaMiddleware();
const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))

const store = createStore( reducer, enhancer)
export default store

建立 store/sagas.js

import {takeEvery, put} from 'redux-saga/effects'
import {GET_MY_LIST} from './actionTypes'
import {getListAction} from './actionCreatores'
import axios from 'axios'

//generator函數
function* mySaga() {
    //等待捕獲action
    yield takeEvery(GET_MY_LIST, getList)
}

function* getList(){
    const res = yield axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList')
    const action = getListAction(res.data)
    yield put(action)
}

export default mySaga;

store/index.js

import { createStore , applyMiddleware ,compose } from 'redux'  //  引入createStore方法
import reducer from './reducer'
import createSagaMiddleware from 'redux-saga'
import mySagas from './sagas'

const sagaMiddleware = createSagaMiddleware();
const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))
const store = createStore( reducer, enhancer)

sagaMiddleware.run(mySagas)

export default store

react-redux

react-redux 不是 redux,
React-Redux 是 Redux 的官方 React 綁定庫。它可以使你的 React 組件從 Redux store 中讀取數據,而且向 store 分發 actions 以更新數據

npm install --save react-redux

是一個提供器,只要使用了這個組件,組件裏邊的其它全部組件均可以使用store了

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList'
import { Provider } from 'react-redux'
import store from './store'
const App = (
    <Provider store={store}>
        <TodoList />
    </Provider>
)
ReactDOM.render(App, document.getElementById('root'));

connect 鏈接器

  • connect 可用來獲取 store 中的數據
  • connect 的做用是把UI組件(無狀態組件)和業務邏輯代碼的分開,而後經過connect再連接到一塊兒,讓代碼更加清晰和易於維護。

先製做映射關係,映射關係就是把原來的state映射成組件中的props屬性

const stateToProps = (state)=>{
    return {
            inputValue: state.inputValue
    }
}

使用 connect 獲取 store 中的數據

import {connect} from 'react-redux'
export default connect(inputValue, null)(TodoList); // 這裏的 inputValue 表明一個映射關係

修改 store 中的數據

例子:當咱們修改中的值時,去改變store數據,UI界面也隨之進行改變。

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

class TodoList extends Component {
    constructor(props){
        super(props)
        this.state = store.getState()
    }
    render() {
        return (
            <div>
                <div>
                    <input value={this.props.inputValue} onChange={this.props.inputChange} />
                    <button>提交</button>
                </div>
                <ul>
                    <li></li>
                </ul>
            </div>
            );
    }
}
const stateToProps = (state)=>{
    return {
        inputValue : state.inputValue
    }
}

const dispatchToProps = (dispatch) =>{
    return {
        inputChange(e){
            console.log(e.target.value)
        }
    }
}

export default connect(stateToProps,dispatchToProps)(TodoList);

派發 action 到 store 中 (再走一遍流程)

const dispatchToProps = (dispatch) =>{
    return {
        inputChange(e){
            let action = {
                type:'change_input',
                value:e.target.value
            }
            dispatch(action)
        }
    }
}

reducer

const defalutState = {
    inputValue : 'jspang',
    list :[]
}
export default (state = defalutState,action) =>{
    if(action.type === 'change_input'){
        let newState = JSON.parse(JSON.stringify(state))
        newState.inputValue = action.value
        return newState
    }
    return state
}

參考資料

  • 嗶哩嗶哩 jspang 的 視頻
  • 相關官方文檔
相關文章
相關標籤/搜索