十四、Redux的使用一
1、javascript纯函数
* javascript纯函数简单总结
- 确定的输入,一定会产生确定的输出
- 函数在执行过程中,不能产生副作用
* React中要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改
* 在redux中,reducer也被要求是一个纯函数
2、为什么需要redux
* react是在视图层帮助我们解决了dom的渲染过程,但是state依然是留给我们自己来管理
- UI = render(state)
* redux就是一个帮助我们管理state的容器:redux是javascript的状态容器,提供了可预测的状态管理
3、redux的核心理念
* store
* action
- 所有数据的变化,必须通过派发(dispatch)action来更新
- action是一个普通的javascript对象,用来描述这次更新的type和content
* reducer
- reducer是一个纯函数
- reducer做的事情就是将传入的state和action结合起来生成一个新的state
4、redux的三大原则
* 单一数据源
* state是只读的
* 使用纯函数来执行修改
5、redux的基本使用
// 安装:npm i redux
const redux = require("redux")
const initialState = {
counter: 0
}
// reducer
function reducer(state = initialState, action) {
switch (action.type) {
case "INCREMENT":
return {...state, counter: state.counter + 1}
case "DECREMENT":
return {...state, counter: state.counter - 1}
case "ADD_NUMBER":
return {...state, counter: state.counter + action.num}
case "SUB_NUMBER":
return {...state, counter: state.counter - action.num}
default:
return state
}
}
// store(创建的时候需要传入一个reducer)
const store = redux.createStore(reducer)
// 订阅store的修改
store.subscribe(() => {
console.log("counter:", store.getState().counter)
})
// actions
const action1 = {type: "INCREMENT"}
const action2 = {type: "DECREMENT"}
const action3 = {type: "ADD_NUMBER", num: 5}
const action4 = {type: "SUB_NUMBER", num: 12}
// 派发action
store.dispatch(action1)
store.dispatch(action2)
store.dispatch(action2)
store.dispatch(action3)
store.dispatch(action4)
6、node中对ES6模块化的支持
* node v13.2.0之前,需要进行如下操作
- 在package.json中添加属性:"type": "module"
- 在执行命令中添加如下选项:node --experimental-modules src/index.js
* node v13.2.0之后,只需要进行如下操作
- 在package.json中添加属性:"type": "module"
* 注意:导入文件时,需要跟上.js后缀名
7、redux的结构化分
import store from "./store/index.js"
import {addAction, subAction, incAction, decAction} from "./store/actionCreators.js"
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch(addAction(10))
store.dispatch(addAction(15))
store.dispatch(subAction(8))
store.dispatch(subAction(5))
store.dispatch(incAction())
store.dispatch(decAction())
import redux from "redux"
import reducer from "./reducer.js"
const store = redux.createStore(reducer)
export default store
import {INCREMENT, DECREMENT, ADD_NUMBER, SUB_NUMBER} from "./constants.js"
export const addAction = num => ({
type: ADD_NUMBER,
num
})
export const subAction = num => ({
type: SUB_NUMBER,
num
})
export const incAction = () => ({
type: INCREMENT
})
export const decAction = () => ({
type: DECREMENT
})
import {INCREMENT, DECREMENT, ADD_NUMBER, SUB_NUMBER} from "./constants.js"
const defaultState = {
counter: 0
}
function reducer(state = defaultState, action) {
switch (action.type) {
case INCREMENT:
return {...state, counter: state.counter + 1}
case DECREMENT:
return {...state, counter: state.counter - 1}
case ADD_NUMBER:
return {...state, counter: state.counter + action.num}
case SUB_NUMBER:
return {...state, counter: state.counter - action.num}
default:
return state
}
}
export default reducer
export const INCREMENT = "INCREMENT"
export const DECREMENT = "DECREMENT"
export const ADD_NUMBER = "ADD_NUMBER"
export const SUB_NUMBER = "SUB_NUMBER"
8、react和redux结合
import React, {PureComponent} from "react";
/**
* 1、react中使用和node中使用的唯一区别:redux引入方式不一样
* - node:import redux from "redux"
* - react:import * as redux from "redux"
*/
import store from "@/store";
import {addAction, subAction} from "@/store/actionCreators";
class Home extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: store.getState().counter
}
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.setState({
counter: store.getState().counter
})
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
return (<div>
<h1>Home</h1>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<button onClick={e => this.addNumber(5)}>+5</button>
</div>)
}
increment() {
store.dispatch(addAction(1))
}
addNumber() {
store.dispatch(addAction(5))
}
}
class About extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: store.getState().counter
}
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.setState({
counter: store.getState().counter
})
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
return (<div>
<h1>About</h1>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={e => this.decrement()}>-1</button>
<button onClick={e => this.subNumber(5)}>-5</button>
</div>)
}
decrement() {
store.dispatch(subAction(1))
}
subNumber() {
store.dispatch(subAction(5))
}
}
export default class App extends PureComponent {
render() {
return (<>
<Home/>
<hr/>
<About/>
</>)
}
}
十五、Redux的使用二
1、redux融入react代码
import React, {PureComponent} from "react";
import {addAction, subAction} from "@/store/actionCreators";
import {connect} from "@/utils/connect"
const Home = connect(state => {
return {
counter: state.counter
}
}, dispatch => {
return {
increment() {
dispatch(addAction(1))
},
addNumber(num) {
dispatch(addAction(num))
}
}
})(class extends PureComponent {
render() {
return (<div>
<h1>Home</h1>
<h2>当前计数:{this.props.counter}</h2>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>)
}
})
const About = connect(state => {
return {
counter: state.counter
}
}, dispatch => {
return {
decrement() {
dispatch(subAction(1))
},
subNumber(num) {
dispatch(subAction(num))
}
}
})(function (props) {
return (<div>
<h1>About</h1>
<h2>当前计数:{props.counter}</h2>
<button onClick={e => props.decrement()}>-1</button>
<button onClick={e => props.subNumber(5)}>-5</button>
</div>)
})
export default class App extends PureComponent {
render() {
return (<>
<Home/>
<hr/>
<About/>
</>)
}
}
import React, {PureComponent} from "react"
import store from "@/store";
export function connect(mapStateToProps, mapDispatchToProp) {
return function enhanceHOC(WrappedComponent) {
return class extends PureComponent {
constructor(props) {
super(props);
this.state = {
storeState: mapStateToProps(store.getState())
}
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.setState({
storeState: mapStateToProps(store.getState())
})
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
return (<WrappedComponent {...this.props}
{...mapStateToProps(store.getState())}
{...mapDispatchToProp(store.dispatch)}/>)
}
}
}
}
2、connect-context脱离业务封装
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './layout/App';
import store from "@/store";
import {StoreContext} from "@/utils/context"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<StoreContext.Provider value={store}>
<App/>
</StoreContext.Provider>);
import React from "react"
const StoreContext = React.createContext()
export {
StoreContext
}
import React, {PureComponent} from "react"
import {StoreContext} from "@/utils/context"
export function connect(mapStateToProps, mapDispatchToProp) {
return function enhanceHOC(WrappedComponent) {
class EnhanceComponent extends PureComponent {
constructor(props, context) {
super(props, context);
this.state = {
storeState: mapStateToProps(context.getState())
}
}
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState({
storeState: mapStateToProps(this.context.getState())
})
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
return (<WrappedComponent {...this.props}
{...mapStateToProps(this.context.getState())}
{...mapDispatchToProp(this.context.dispatch)}/>)
}
}
EnhanceComponent.contextType = StoreContext
return EnhanceComponent
}
}
3、react-redux库的使用
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './layout/App';
import store from "@/store";
// 变更处(npm i react-redux)
import {Provider} from "react-redux"
const root = ReactDOM.createRoot(document.getElementById('root'));
// 变更处
root.render(<Provider store={store}>
<App/>
</Provider>);
import React, {PureComponent} from "react";
import {addAction, subAction} from "@/store/actionCreators";
// 变更处
import {connect} from "react-redux"
const Home = connect(state => {
return {
counter: state.counter
}
}, dispatch => {
return {
increment() {
dispatch(addAction(1))
},
addNumber(num) {
dispatch(addAction(num))
}
}
})(class extends PureComponent {
render() {
return (<div>
<h1>Home</h1>
<h2>当前计数:{this.props.counter}</h2>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>)
}
})
const About = connect(state => {
return {
counter: state.counter
}
}, dispatch => {
return {
decrement() {
dispatch(subAction(1))
},
subNumber(num) {
dispatch(subAction(num))
}
}
})(function (props) {
return (<div>
<h1>About</h1>
<h2>当前计数:{props.counter}</h2>
<button onClick={e => props.decrement()}>-1</button>
<button onClick={e => props.subNumber(5)}>-5</button>
</div>)
})
export default class App extends PureComponent {
render() {
return (<>
<Home/>
<hr/>
<About/>
</>)
}
}
4、组件中异步操作
import {connect} from "react-redux";
import {addAction} from "@/store/actionCreators";
import React, {PureComponent} from "react";
export default connect(state => {
return {
counter: state.counter
}
}, dispatch => {
return {
increment() {
dispatch(addAction(1))
},
addNumber(num) {
dispatch(addAction(num))
}
}
})(class extends PureComponent {
componentDidMount() {
setTimeout(() => {
this.props.addNumber(10)
}, 3000)
}
render() {
return (<div>
<h1>Home</h1>
<h2>当前计数:{this.props.counter}</h2>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>)
}
})
5、redux中异步操作
import * as redux from "redux"
import reducer from "./reducer.js"
// npm i redux-thunk
import thunkMiddleware from "redux-thunk"
// 应用一些中间件
const storeEnhancer = redux.applyMiddleware(thunkMiddleware)
const store = redux.createStore(reducer, storeEnhancer)
export default store
import {connect} from "react-redux";
import {addAction, requestAction} from "@/store/actionCreators";
import React, {PureComponent} from "react";
export default connect(state => {
return {
counter: state.counter
}
}, dispatch => {
return {
increment() {
dispatch(addAction(1))
},
addNumber(num) {
dispatch(addAction(num))
},
requestNumber() {
dispatch(requestAction)
}
}
})(class extends PureComponent {
componentDidMount() {
this.props.requestNumber()
}
render() {
return (<div>
<h1>Home</h1>
<h2>当前计数:{this.props.counter}</h2>
<button onClick={e => this.props.increment()}>+1</button>
<button onClick={e => this.props.addNumber(5)}>+5</button>
</div>)
}
})
- src/store/actionCreators.js
import {INCREMENT, DECREMENT, ADD_NUMBER, SUB_NUMBER} from "./constants.js"
export const addAction = num => ({
type: ADD_NUMBER,
num
})
export const subAction = num => ({
type: SUB_NUMBER,
num
})
export const incAction = () => ({
type: INCREMENT
})
export const decAction = () => ({
type: DECREMENT
})
export const requestAction = (dispatch, getState) => {
setTimeout(() => {
console.log(getState())
dispatch(addAction(10))
}, 3000)
}
import * as redux from "redux"
import reducer from "./reducer.js"
import thunkMiddleware from "redux-thunk"
/**
* 1、浏览器扩展redux-devtools,需要有如下配置
* - window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__返回composeEnhancers函数
*/
const composeEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true})) || redux.compose
const storeEnhancer = redux.applyMiddleware(thunkMiddleware)
const store = redux.createStore(reducer, composeEnhancers(storeEnhancer))
export default store
2、generator和Promise一起来使用
function* bar() {
console.log(111)
const result = yield new Promise(resolve => {
setTimeout(() => {
resolve(222)
}, 3000)
})
console.log(result)
}
const it = bar()
it.next().value.then(res => {
it.next(res)
})
3、redux-saga
import * as redux from "redux"
import reducer from "./reducer.js"
import thunkMiddleware from "redux-thunk"
// npm i redux-saga
import createSagaMiddleware from "redux-saga"
import saga from "@/store/saga";
const composeEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true})) || redux.compose
// 创建sagaMiddleware中间件
const sagaMiddleware = createSagaMiddleware()
const storeEnhancer = redux.applyMiddleware(thunkMiddleware, sagaMiddleware)
const store = redux.createStore(reducer, composeEnhancers(storeEnhancer))
sagaMiddleware.run(saga)
export default store
import {all, takeLatest, takeEvery, put} from "redux-saga/effects"
import {FETCH_HOME_MULTIDATA} from "./constants.js"
import {addAction} from "./actionCreators"
function* fetchHomeMultidata(action) {
const res = yield new Promise(resolve => {
setTimeout(() => {
resolve(10)
}, 3000)
})
/**
* 2、等价于
* - yield put(changeBannerAction(banners))
* - yield put(changeRecommendAction(recommends))
*/
yield all([
yield put(addAction(res)),
yield put(addAction(res))
])
}
function* mySaga() {
/**
* 1、takeLatest和takeEvery区别
* - takeLatest:一次只能监听一个对应的action
* - takeEvery:每一个都会被执行
*/
yield all([
takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultidata),
takeLatest(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
])
}
export default mySaga;
- src/store/actionCreators.js
import {ADD_NUMBER, SUB_NUMBER, FETCH_HOME_MULTIDATA} from "./constants.js"
export const addAction = num => ({
type: ADD_NUMBER,
num
})
export const subAction = num => ({
type: SUB_NUMBER,
num
})
export const requestAction = {
type: FETCH_HOME_MULTIDATA
}
export const INCREMENT = "INCREMENT"
export const DECREMENT = "DECREMENT"
export const ADD_NUMBER = "ADD_NUMBER"
export const SUB_NUMBER = "SUB_NUMBER"
export const FETCH_HOME_MULTIDATA = "FETCH_HOME_MULTIDATA"
4、中间件实现原理
import store from "./store.js"
import {addAction} from "./actionCreators.js"
// 1、基本做法
// console.log("dispatch前---dispatching action:", addAction(10))
// store.dispatch(addAction(10))
// console.log("dispatch后---new state:", store.getState())
// console.log("dispatch前---dispatching action:", addAction(15))
// store.dispatch(addAction(15))
// console.log("dispatch后---new state:", store.getState())
// 2、封装一个函数
// function dispatchAndLogging(action) {
// console.log("dispatch前---dispatching action:", action)
// store.dispatch(action)
// console.log("dispatch后---new state:", store.getState())
// }
// dispatchAndLogging(addAction(10))
// dispatchAndLogging(addAction(15))
// 3、函数的基础之上进行优化:修改原有的dispatch(hack技术:monkeyingpatch)
// const next = store.dispatch
// function dispatchAndLogging(action) {
// console.log("dispatch前---dispatching action:", action)
// next(action)
// console.log("dispatch后---new state:", store.getState())
// }
// store.dispatch = dispatchAndLogging
// store.dispatch(addAction(10))
// store.dispatch(addAction(15))
// 4、将之前的操作进行封装(封装patchLogging的代码)
function patchLogging(store) {
const next = store.dispatch
function dispatchAndLogging(action) {
console.log("dispatch前---dispatching action:", action)
next(action)
console.log("dispatch后---new state:", store.getState())
}
// store.dispatch = dispatchAndLogging
return dispatchAndLogging
}
// 封装patchThunk的功能
function patchThunk() {
const next = store.dispatch
function dispatchAndThunk(action) {
if (typeof action === "function") {
action(store.dispatch, store.getState)
} else {
next(action)
}
}
return dispatchAndThunk
}
// patchLogging(store)
// patchThunk(store)
// store.dispatch(addAction(10))
// store.dispatch(addAction(15))
// function foo(dispatch, getState) {
// store.dispatch(addAction(10))
// }
// store.dispatch(foo)
// 5、封装applyMiddleware
function applyMiddleware(...middlewares) {
// const newMiddleware = [...middlewares]
middlewares.forEach(middleware => {
store.dispatch = middleware(store)
})
}
applyMiddleware(patchLogging, patchThunk)
store.dispatch(addAction(10))
store.dispatch(addAction(15))
5、redux-reducer拆解
import {CHANGE_PAGEINFO, CHANGE_TOTAL} from "./constants.js"
const pageInfo = {
pageNum: 1,
pageSize: 10,
}
function pageInfoReducer(state = pageInfo, action) {
switch (action.type) {
case CHANGE_PAGEINFO:
return {...state, ...action.data}
default:
return state
}
}
const total = 0
function totalReducer(state = total, action) {
switch (action.type) {
case CHANGE_TOTAL:
return action.data
default:
return state
}
}
function reducer(state = {}, action) {
return {
pageInfo: pageInfoReducer(state.pageInfo, action),
total: totalReducer(state.total, action)
}
}
export default reducer
6、combineReducers函数
import {CHANGE_PAGEINFO, CHANGE_TOTAL} from "./constants.js"
// react中引入方式:import {combineReducers} from "redux"
import redux from "redux"
const pageInfo = {
pageNum: 1,
pageSize: 10,
}
function pageInfoReducer(state = pageInfo, action) {
switch (action.type) {
case CHANGE_PAGEINFO:
return {...state, ...action.data}
default:
return state
}
}
const total = 0
function totalReducer(state = total, action) {
switch (action.type) {
case CHANGE_TOTAL:
return action.data
default:
return state
}
}
// function reducer(state = {}, action) {
// return {
// pageInfo: pageInfoReducer(state.pageInfo, action),
// total: totalReducer(state.total, action)
// }
// }
// reducer应该是一个什么类型?function
const reducer = redux.combineReducers({
pageInfo: pageInfoReducer,
total: totalReducer
})
export default reducer
7、react中的state如何管理
* 目前我们已经主要学习了三种状态管理方式
- 方式一:组件中自己的state管理
- 方式二:context数据的共享状态
- 方式三:redux管理应用状态