先來看看 React 一些特點和沒有解決的問題:
- 通信:組件之間如何通信?react 采用傳參,對于大應用,很不方便。
- 數據流:數據如何和視圖串聯起來?路由和數據如何綁定?如何編寫異步邏輯?等等
為了解決這些問題引入 redux,前幾天看見一個博客,把 redux 的原理結合動圖把為什么使用 redux 講的特別透徹。
一定要看:Redux設計思想與使用場景
同時配上這篇:redux 設計思想
總結一下就是:
redux 的誕生是為了給 React 應用提供「可預測化的狀態管理」機制。
Redux 會將整個應用狀態(其實也就是數據)存儲到到一個地方,稱為 store。
這個 store 里面保存一棵狀態樹(state tree)
組件改變 state 的唯一方法是通過調用 store 的 dispatch 方法,觸發一個 action,這個action 被對應的 reducer 處理,于是 state 完成更新。
組件可以派發 (dispatch) 行為 (action) 給 store,而不是直接通知其它組件
其它組件可以通過訂閱 store 中的狀態 (state) 來刷新自己的視圖
一、初識 Redux
網站重要的開門注:
redux a predictable state container for JavaScript apps.
Redux是一個可預測狀態容器。
為了便于直觀理解,演示先不配合 React 使用,全部在一個文件 1.js
里面寫。使用 node 進行調試。
先安裝 redux。
npm install --save redux
實例代碼如下:
const redux = require("redux");
//①創建一個純函數reducers。
//這個函數接收兩個參數:一個 state、一個 action,并返回新的 state。
const reducers = (state = {a : 10},action)=>{
if(action.type === "ADD"){
return {
a : state.a + 1
}
}else if(action.type === "UNADD"){
return {
a : state.a - 1
}
}
return state;
}
// ②創建store
const store = redux.createStore(reducers);
// ③通過getState()方法可訪問state
console.log(store.getState().a);//10
// ④通過dispatch方法{type:"ADD"}進行一次加法運算
store.dispatch({type:"ADD"});
console.log(store.getState().a);//11
// ⑤通過dispatch方法{type:"UNADD"}進行一次減法運算
store.dispatch({type:"UNADD"});
console.log(store.getState().a);//10
- redux 依賴于一個純函數(reducer),它描述了 action 如何將 state 轉為新的 state,action 只說明了什么事情發生,但是不會描述 state 如何改變。絕對不能改變 state 對象,如果你要變,只能返回新的 state。什么是純函數?不改變傳入的參數的函數就是純函數。
- redux 通過 createStore 讓 store 和 reducers 產生聯系。
- 產生聯系的 store ,提供一個
getState()
方法來訪問純函數的 state,通過dispatch({"type":...})
來檢測命令。唯一能夠改變 state 的方法就是 dispatch 一個 action。store 就是統一管理 reducer 和 action 的,將所有的一切統一起來的。 - store 有三個主要的功能:
- 持有 app 的 state
- 允許通過 getState() 得到 state
- 允許通過 dispatch 來改變 state
注意的是:可以有多個純函數,但只能有 1 個 store 。
- 我們知道 action 是 store 唯一的信息的來源且 action 需要被 dispatch() 函數派發,action 是純的、扁平的 JavaScript 對象。
如果需要多個純函數,則需要引入,combineReducers()
這個方法進行純函數合并。
const redux = require("redux");
//①創建一個純函數reducers。
const reducers = (state = {a : 10},action)=>{
if(action.type === "ADD"){
return {
a : state.a + 1
}
};
return state;
}
//⑥創建另一個純函數reducers2。
const reducers2 = (state = {a : 100},action)=>{
if(action.type === "LOAD"){
return {
a : action.palyload
}
}
return state;
}
// ⑦多個需求只能拆分純函數reducer,通過combineReducers合并成一個
const reducer = redux.combineReducers({
reducers,
reducers2
})
// ②創建store
const store = redux.createStore(reducer);
// ③通過getState()方法可訪問state的加命名空間
console.log(store.getState().reducers.a);//10
// 但是通過dispatch卻不用添加命名空間
store.dispatch({type:"ADD"});
console.log(store.getState().reducers.a);//11
//<============華麗的分割線===========>
// ③通過getState()方法可訪問state的加命名空間
console.log(store.getState().reducers2.a);//100
// action可通過playload攜帶負載參數
store.dispatch({type:"LOAD",palyload:"我是攜帶的負載!"});
console.log(store.getState().reducers2.a);//我是攜帶的負載
純函數里面的 action 就是由 type 屬性的組成的 JSON,但實際上不僅僅有 type 屬性,還可以有其他的屬性,所有其他的屬性都叫做載荷(payload)。
二、redux 結合 React
先安裝:react-redux粘合劑官網:https://react-redux.js.org/
npm install --save react-redux
梳理一下 redux 和 react-redux 提供各自提供的 API。
- Redux 是可預測狀態容器(reducer、state、store、dispatch、action、applyMiddleware 等API)
- react-redux 提供 Provider 和 connect 。
再看項目主要目錄結構:
┣? main.js
┣? index.html
┣? webpack.config.js
┣? package.json
┗? views
┣? app
┣? App.js
┗? store
┣? index.js
┗? counterStore.js
展示項目目錄代碼:
- couterStore.js 放入純函數:
export default (state = {a : 10},action)=>{
if(action.type === "ADD"){
return {
a : state.a + 1
}
}else if(action.type === "UNADD"){
return {
a : state.a - 1
}
};
return state;
}
使用 redux 第一步必須是寫純函數。
- index.js
import counterStore from "./counterStore.js";
import {combineReducers} from "redux";
export default combineReducers({
counterStore
});
使用 combineReducers
進行純函數合并,變成一個統領文件
。
- main.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/app/App.js";
import {createStore} from "redux";
import {Provider} from "react-redux";
import reducers from "./views/store";
const store = createStore(reducers);
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
,
document.getElementById("app")
);
引入 createStore 函數、引入r educers 統領文件、創建 store、可以測試使用store.getState().a
輸出結果。Provider 可以讓 store 照耀在 APP 里面所有的組件上,做到天下無人不識君。Provider 的機理是使用的 context 上下文機理。
- App.js
connect 這里是個高階函數的用法,很明顯 connect 進行了柯粒化處理。這里叫做裝飾器。裝飾器兩個(mapStateToProps)(mapDispatchToProps)
,第一圓括號內部寫如何裝飾,映射全局 state 到組件的 props;第二個圓括號內部寫裝飾誰,將dispatch這個詞語映射到 props 上。
寫法一:不使用裝飾器
import React,{Component} from "react";
import {connect} from "react-redux";
class App extends Component {
constructor(){
super()
}
render(){
return(
<div>
<h1>{this.props.a}</h1>
<button onClick = {this.props.add}>按我加一</button>
</div>
);
}
}
export default connect(
({counterStore})=>({
a : counterStore.a
}),
dispatch=>({
add(){
dispatch({type:"ADD"});
}
})
)(App);
寫法二:裝飾器寫法
react 不支持裝飾器寫法,首先安裝語法糖。
npm install --save @babel/plugin-proposal-decorators
參考 babel 官網 @babel/plugin-proposal-decorators · Babel
webpack.config.js 進行配置:
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }]
]
}
配置完,重新開啟項目即可使用,改寫代碼:
import React,{Component} from "react";
import {connect} from "react-redux";
@connect(
({counterStore})=>({
a : counterStore.a
}),
dispatch=>({
dispatch
})
)
export default class App extends Component {
constructor(){
super()
}
render(){
return(
<div>
<h1>{this.props.a}</h1>
<button onClick = {()=>{this.props.dispatch({type:"ADD"})}}>按我加一</button>
</div>
);
}
}
兩者實現的效果都是一樣的。打開http://127.0.0.1:8080/
,查看效果:
三、bindActionCreators(基本不用)
Action Creator 就是一個創建 action 的函數,例如:() => ({“type” : “ADD”})
。不要混淆 action 和 action creator 這兩個概念。Action 是一個信息的負載,而 action creator 是一個創建 action 的工廠。調用 action creator 只會生產 action,但不分發。bindActionCreators 這個 API 就是使用 Action Creator。
使用場景?所以是基本沒用。
惟一會使用到
bindActionCreators
的場景是當你需要把 action creator 往下傳到一個組件上,卻不想讓這個組件覺察到 Redux 的存在,而且不希望把dispatch
或 Redux store 傳給它。
bindActionCreators(actionCreators, dispatch)
參數
actionCreators
(Function or Object): 一個 action creator,或者一個 value 是 action creator 的對象。
更改 App.js
import React,{Component} from "react";
import {connect} from "react-redux";
import * as actionCreators from "./actionCreators.js";
import {bindActionCreators} from "redux";
@connect(
({counterStore})=>({
a : counterStore.a
}),
(dispatch) => ({
actionCreators: bindActionCreators(actionCreators , dispatch)
})
)
export default class App extends Component {
constructor(){
super()
}
render(){
return(
<div>
<h1>{this.props.a}</h1>
<button onClick = {this.props.actionCreators.add}>按我加一</button>
</div>
);
}
}
新增的actionCreators文件
export const add = ()=>({type:"ADD"});
加法器仍然起作用。
四、安裝 redux-logger
redux-logger 是每次 dispatch 的記錄器。當我們啟用 logger 的時候每次 dispatch ,都會在控制臺輸出每次詳情。而我們只需要一裝一引一配。
一裝 redux-logger
npm install --save redux-logger
在主入口文件 main.js 中進行 一引一配
。
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/app/App.js";
import {createStore,applyMiddleware} from "redux";
import {Provider} from "react-redux";
import reducers from "./views/store";
import logger from "redux-logger";
const store = createStore(reducers,applyMiddleware(logger));
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
,
document.getElementById("app")
);
輸入網址:http://127.0.0.1:8080/
查看控制臺效果。
五、redux 的 hooks
hooks 在 v7.1.0 中首次被添加。這有一篇很好的文章,來介紹新出的 hook API 。
【譯】React Redux API Hooks