redux重記
入門
redux入門先要使用兩個東西,一個是 reducer,一個是 store。
先說 reducer,是一個會被 redux store 調(diào)用的一個函數(shù),會接到兩個參數(shù):state 和 action。
然后根據(jù) action 的類型和 action 攜帶的參數(shù)來執(zhí)行一些修改state的操作,我們先不管 action
const reducer = (state, action) => {
switch (action.type) {
case '???':
return '本次reducer處理完的數(shù)據(jù)'
default:
return 'reducer默認(rèn)數(shù)據(jù)'
}
}
export default reducer
store 的建立必須依賴一個 reducer,使用 createStore 這個來自 redux 庫的 api,然后傳入一個 reducer ,就會生成一個 store,這個store,就是給其他組件用的大倉庫。
import { legacy_createStore as createStore } from "redux";
import reducer from './reducer'
const store = createStore(reducer)
export default store
我們找個地方看一下 store 長啥樣,控制臺輸出可以看到,store 帶有dispatch,getState等方法
[圖片上傳失敗...(image-4d903-1658743707093)]
調(diào)用一下 getState ,就會發(fā)現(xiàn)得到 reducer 的默認(rèn)值
import store from '../../store'
console.log(store.getState()) //輸出 'reducer默認(rèn)數(shù)據(jù)'
此時意識到 reducer 那個 default 的 return 就是 store 的初始值,我們改寫一下 store
const defaultState = {
count: 0
}
const reducer = (state = defaultState, action) => {
switch (action.type) {
case '???':
return '本次reducer處理完的數(shù)據(jù)'
default:
return state
}
}
export default reducer
?補充知識:第一次 createStore 調(diào)用 reducer的時候,state 是 undefined ,action 是名為 {
type: "@@redux/INITk.0.v.x.n.o"} 的一個對象,因為這個 state 實際上是 preState即 前一個狀態(tài)
此時在組件中調(diào)用 store.getState() 就可以讀到 store 里面的數(shù)據(jù)了。
import store from '../../store'
...
<h1>當(dāng)前求和為:{store.getState().count}</h1>
入門階段先告一段落,我們可以知道,store貌似什么都沒有,實際數(shù)據(jù)都在 reducer 上,這點很重要。
上手
此時我們知道了 reducer 才是賣力工作那個,我們看回來那個入門階段沒管的 action 與 store 的 dispatch
action 是一個我們約定好的對象,包含 type 與 data 給 reducer 處理。怎么給?在組件調(diào)用 store.dispatch(action) 這種形式給
import store from '../../store'
某個方法 = ()=>{
store.dispatch({
type: '派發(fā)的action名',
data: reducer 可以用的數(shù)據(jù)
})
}
實例:
increment = () => {
let data = this.select.value
store.dispatch({
type: 'increment',
data
})
}
此時我們回到 reducer 中處理,注意 return 的值會修改整個原始 state 對象,我們用合并方式處理
const defaultState = {
count: 0
}
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'increment':
console.log(state);
return { ...state, count: state.count + action.data * 1 }
default:
return state
}
}
export default reducer
此時又可以看到state 確實變了,但頁面沒有更新,那是因為想要頁面響應(yīng)式,必須調(diào)用 setState,forceUpdate 等方法,如何檢測到 store 里的變化后就調(diào)用頁面相應(yīng)呢?
store 提供了 subscribe 這個 api,這個 api 會接受一個回調(diào)函數(shù),這個回調(diào)函數(shù)會在store里的數(shù)據(jù)變化時調(diào)用。我們現(xiàn)在可以讓組件在掛載時就讓store檢測到變化就調(diào)用 setState({}) 假動作演一下組件
componentDidMount() {
store.subscribe(() => {
this.setState({})
})
}
當(dāng)然可以狠一點直接放去 index.js 的 ReactDOM.render 外面
上手完善
我們已經(jīng)了解了 reducer 與 store 與 action 了,實際工作上,我們還會把 action 作為一個單獨文件提取出來,并把 action 的 type 也提取出來用變量來代替字符串
//type 提取 constant.js 文件
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
//action 提取 action.js 文件
import * as actionType from './constant'
export const createIncrementAction = (data) => ({
type: actionType.INCREMENT,
data
})
export const createDecrementAction = (data) => ({
type: actionType.DECREMENT,
data
})
組件使用的時候
import * as actionCreators from '../../store/action'
increment = () => {
let data = this.select.value
store.dispatch(actionCreators.createIncrementAction(data))
}
進階 異步action
action 除了可以是對象類型,還可以是函數(shù)類型,一般對象類型的是同步 action ,函數(shù)類型就是異步 action。
我們創(chuàng)建一個異步 action
import * as actionType from './constant'
import store from './index'
export const createIncrementAction = (data) => ({
type: actionType.INCREMENT,
data
})
export const createIncrementAsyncAction = (data, time) => {
return () => {
setTimeout(() => {
store.dispatch(createIncrementAction(data))
}, time)
}
}
然后給組件派發(fā)
asyncIncrement = () => {
const { value } = this.select
store.dispatch(actionCreators.createIncrementAsyncAction(value, 500))
}
然后會發(fā)現(xiàn)報錯,說如果想使用非對象類型的 action ,需要使用中間件比如 redux-thunk
npm i redux-thunk 接下來就是死記硬背了,在 createStore 時傳入第二個參數(shù),該參數(shù)需要從 redux 引入 applyMiddleware 和從 redux-thunk 引入 thunk 最后組合成 applyMiddleware(thunk)
import { legacy_createStore as createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from './reducer'
const store = createStore(reducer, applyMiddleware(thunk))
export default store
然后我們仔細(xì)看一下組件和action的代碼會發(fā)現(xiàn)調(diào)用了兩次 store.dispatch,實際上在 action 返回的函數(shù)中我們可以接到一個 dispatch,就不用引入 store 再 dispatch 了
import * as actionType from './constant'
export const createIncrementAction = (data) => ({
type: actionType.INCREMENT,
data
})
export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
}
react-redux
react-redux 將使用 redux 的組件分離成容器組件和 UI 組件,UI 組件不處理任何與 store 相關(guān)的操作。全部交給容器組件處理。容器通過 props 把數(shù)據(jù)傳給 UI 組件。創(chuàng)建容器組件需要借助 react-redux 上的 connect,在第二個括號放入 UI 組件
class Count extends Component{
...
}
export default connect()(Count)
此時我們使用容器組件,會發(fā)現(xiàn)報錯說找不到 store,我們需要在使用容器組件時通過props傳入store
import Count from './component/Count'
import store from './store'
export default class App extends Component {
render() {
return (
<div>
<Count store={store} />
</div>
)
}
}
容器通過 connect 的第一個參數(shù)把 store 的 state 傳給 UI 組件,第一個參數(shù)叫 mapStateToProps ,是一個接收到 store 的state為形參的函數(shù)。返回值的對象就是作為 props 傳給 UI 組件的對象
const mapStateToProps = (state)=>{
return {
count: state.count
}
}
容器通過 connect 的第二個參數(shù)把 store 的 dispatch 傳遞給 UI 組件,第二個參數(shù)叫 mapDispatchToProps,是一個接收 store 的 dispatch 為形參的函數(shù),返回值的對象就是作為 props 傳給 UI 組件的對象
const mapDispatchToProps = (dispatch) => {
return {
increment: value => { dispatch(actionCreators.createIncrementAction(value)) },
decrement: value => { dispatch(actionCreators.createDecrementAction(value)) }
}
}
此時只需要 export default connect(mapStateToProps, mapDispatchToProps)(index) 就好了
簡寫優(yōu)化
可以看到 mapStateToProps 和 mapDispatchToProps 最終返回的都是一個對象,可以觸發(fā)直接返回對象的簡寫形式,但針對 mapDispatchToProps 還有一項優(yōu)化,那就是 mapDispatchToProps 不僅可以寫成方法,還可以寫成對象。
那么寫成對象 dispatch去哪了?參數(shù)去哪傳?dispatch 的話 react-redux 會自動幫你調(diào)用,參數(shù)直接往 actionCreate 里傳
const mapStateToProps = (state) => ({
count: state.count
})
const mapDispatchToProps = {
increment: actionCreators.createIncrementAction,
decrement: actionCreators.createDecrementAction
}
export default connect(mapStateToProps, mapDispatchToProps)(index)
省略 store.subscribe
我們在 redux 中還需要手動調(diào)用 store.subscribe ,在 react-redux 中發(fā)現(xiàn)不使用也會自動更新視圖,實際上是因為 react-redux 之所以分離 容器組件和 UI組件,就是因為 connect 會自動更新 UI 組件
Provider
我們在使用容器組建的時候會傳入一個store={store},如果組件一多,豈不是每個組件都要傳一次?
此時我們需要使用 Provider ,外面包一層 <Provider store={store}> 即可,這個組件是從 react-redux 中引入的,我們可以用它直接包住 App 組件
import store from "./store";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
也可以在項目中看到在 App 組件中用 Provider 包住 children
render() {
return (
<Provider store={store}>
<React.Fragment>
{this.state.env && this.props.children}
</React.Fragment>
</Provider>
);
}
處理多個 store ,使用 combineReducers
使用完 combineReducers 后,我們來看一下 store 總文件。
import { legacy_createStore as createStore, applyMiddleware, combineReducers } from "redux";
import thunk from "redux-thunk";
import countReducer from '../component/Count/store/reducer'
import personReducer from '../component/Person/store/reducer'
const reducer = combineReducers({
countReducer,
personReducer
})
const store = createStore(reducer, applyMiddleware(thunk))
export default store
在其他容器組件中,讀取 store 的內(nèi)容就變成了
const mapStateToProps = (state) => ({
persons: state.personReducer.persons,
count: state.countReducer.count
})
mapDispatchToProps 沒變,因為 action 的生成不依賴 store。
工作項目中還能看到 immutable 配合 redux-immutable 的,combineReducers 從 redux-immutable中引入。
實際上還能把 reducer 單獨提成一個文件,在 store 引入就行。
import { combineReducers } from 'redux'
import countReducer from '../component/Count/store/reducer'
import personReducer from '../component/Person/store/reducer'
export default combineReducers({
countReducer,
personReducer
})
配置 redux 開發(fā)者工具
store 中
import {composeWithDevTools} from 'redux-devtools-extension'
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
純函數(shù)
只要是同樣的輸入,必定得到同樣的輸出。遵循以下約束
- 不得改寫原參數(shù)數(shù)據(jù)
- 不會產(chǎn)生任何副作用,例如網(wǎng)絡(luò)請求,輸入輸出設(shè)備
- 不能調(diào)用 Date.now() 或 Math.random() 等不純的方法
redux 中的 reducer 必須是一個純函數(shù)