簡介
- 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。
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 中的數據
import store from './store'
// ...
constructor(props){
super(props)
console.log(store.getState())
}
- 先引入store
- 使用 getState 函數獲取數據
安裝 Redux DevTools
chrome 搜索插件 Redux DevTools 並安裝
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 數據的唯一來源。
創建 action
const action ={
type:'',
value: ''
}
store.dispatch(action)
- type 字段來表示將要執行的動作(必須要有)
- 除了 type 字段外,action 對象的結構完全自由
- 使用 dispatch 函數發送數據到 store
更改 Reducer
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
更新組件數據
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 更方便些。不過,在大型應用中把它們顯式地定義成常量還是利大於弊的。
actionTypes.js
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
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 的 視頻
- 相關官方文檔