# Reducer
# 什么是reducer (opens new window)
reducer 就是⼀个纯函数,接收旧的 state 和 action,返回新的 state。
之所以将这样的函数称之为 reducer,是因为这种函数与被传入Array.prototype.reduce(reducer, ?initialValue) (opens new window) 里的回调函数属 于相同的类型。保持 reducer 纯净非常重要。永远不要在 reducer 里做这
些操作:
修改传入参数;
执行有副作用的操作,如 API 请求和路由跳转;
调用非纯函数,如 Date.now() 或 Math.random()。
# 什么是reduce
此例来自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator
+ currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
思考:有如下函数, 想要顺序输出1 2 3,如何处理。
function f1() {
console.log("f1");
}
function f2() {
console.log("f2");
}
function f3() {
console.log("f3");
}
// 想要顺序输出1 2 3
// f1();f2();f3();
// f3(f2(f1()));
compose(
f1,
f2,
f3,
)();
function compose(...funcs) {
if(funcs.length===0){
return arg=>arg
}else if(funcs.length===1){
return funcs[0]
}
return funcs.reduce((left,right)=>(...args)=>right(left(...args)))
}
# Redux 上手
Redux是JavaScript应用的状态容器。它保证程序行为⼀致性且易于测试。
# 安装redux
npm install redux --save
# redux上手
redux较难上手,是因为上来就有太多的概念需要学习,用⼀个累加器举例
需要⼀个store来存储数据
store里的reducer初始化state并定义state修改规则
通过dispatch⼀个action来提交对数据的修改
action提交到reducer函数里,根据传入的action的type,返回新的 state
创建store,src/store/ReduxStore.js
import { createStore } from 'redux'
const counterReducer = (state = 0, action) => {
console.log(state);
switch (action.type) {
case 'add':
return state + 1
case 'minus':
return state - 1
default:
return state
}
}
const store = createStore(counterReducer)
export default store
创建ReduxPage
import React, { Component } from 'react';
import store from '../store/reduxStore'
class ReduxPage extends Component {
componentDidMount(){
store.subscribe(()=>{
this.forceUpdate()
// this.setState({})
})
}
render() {
console.log('ReduxPage',store);
return (
<div>
<h1>ReduxPage</h1>
<p>{store.getState()}</p>
<button onClick={()=>{store.dispatch({type:"add"})}}>add</button>
</div>
);
}
}
export default ReduxPage;
# react-redux
每次都重新调用render和getState太low了,想用更react的方式来写,需要react-redux的支持
npm install react-redux --save
# 提供了两个api
Provider 为后代组件提供store
connect 为组件提供数据和变更方法
全局提供store,index.js
import React from 'react';
import ReactReduxPage from './pages/ReactReduxPage.jsx';
import './App.css';
import { Provider } from 'react-redux';
import store from './store/reduxStore'
function App() {
return (
<Provider store={store}>
<div className="App">
<ReactReduxPage></ReactReduxPage>
</div>
</Provider>
);
}
export default App;
import React, { Component } from 'react';
import { connect } from "react-redux";
class ReduxPage extends Component {
render() {
console.log('ReduxPage',this.props);
const {num,add,minus} = this.props
return (
<div>
<h1>ReduxPage</h1>
<p>{num}</p>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
num: state
}
}
const mapDispatchToProps = dispatch => {
return {
add: () => {
dispatch({type:'add'})
},
minus: () => {
dispatch({type:'minus'})
},
}
}
export default connect(
mapStateToProps, //状态映射
mapDispatchToProps //派发事件映射
)(ReduxPage);
connect中的参数:state映射和事件映射
# 异步
Redux只是个纯粹的状态管理器,默认只支持同步,实现异步任务 比如延 迟,网络请求,需要中间件的支持,比如我们试用最简单的redux-thunk和 redux-logger
npm install redux-thunk redux-logger --save
应用中间件,store.js
import { createStore,applyMiddleware } from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import counterReducer from './counterReducer'
const store = createStore(counterReducer,applyMiddleware(logger,thunk))
export default store
使用异步操作时的变化
const mapDispatchToProps = {
asyncAdd: ()=>dispatch => {
setTimeout(()=>{
dispatch({type:'add'})
},1000)
},
add: () => {
return {type:'add'}
},
minus: () => {
return {type:'minus'}
},
}
# 代码抽取
抽离reducer和action:
- 抽离action
action/reactReduxPage.js
export const asyncAdd = () => dispatch => {
setTimeout(() => {
dispatch({ type: 'add' })
}, 1000)
}
export const add = () => {
return { type: 'add' }
}
export const minus = () => {
return { type: 'minus' }
}
//对应的ReactReduxPage文件直接引用
import React, { Component } from 'react';
import { connect } from "react-redux";
import { add, minus, asyncAdd } from
"../action/reactReduxPage";//此处直接引用
class ReduxPage extends Component {
render() {
console.log('ReduxPage',this.props);
const {num,add,minus,asyncAdd} = this.props
return (
<div>
<h1>ReduxPage</h1>
<p>{num}</p>
<button onClick={asyncAdd}>asyncAdd</button>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
num: state
}
}
const mapDispatchToProps = {
add, minus, asyncAdd
}
// const mapDispatchToProps = dispatch => {
// return {
// asyncAdd: () => {
// setTimeout(()=>{
// dispatch({type:'add'})
// },1000)
// },
// add: () => {
// dispatch({type:'add'})
// },
// minus: () => {
// dispatch({type:'minus'})
// },
// }
// }
export default connect(
mapStateToProps, //状态映射
mapDispatchToProps //派发事件映射
)(ReduxPage);
- 抽离reducer
store/counterReducer.js
export const counterReducer = (state = 0, action) => {
console.log(state);
switch (action.type) {
case 'add':
return state + 1
case 'minus':
return state - 1
default:
return state
}
}
store/index.js也是直接引用counterReducer即可
import { createStore,applyMiddleware } from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import {counterReducer} from './counterReducer'
const store = createStore(counterReducer,applyMiddleware(logger,thunk))
export default store
# 模块化
combineReducers,store/index.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import { counterReducer } from './counterReducer'
const store = createStore(combineReducers({ counter: counterReducer }), applyMiddleware(logger, thunk))
export default store
ReactReduxPage.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import { add, minus, asyncAdd } from
"../action/reactReduxPage";//此处直接引用
class ReduxPage extends Component {
render() {
console.log('ReduxPage',this.props);
const {num,add,minus,asyncAdd} = this.props
return (
<div>
<h1>ReduxPage</h1>
<p>{num}</p>
<button onClick={asyncAdd}>asyncAdd</button>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
num: state.counter
}
}
const mapDispatchToProps = {
add, minus, asyncAdd
}
export default connect(
mapStateToProps, //状态映射
mapDispatchToProps //派发事件映射
)(ReduxPage);
# redux原理
# 核心实现
存储状态state
获取状态getState
更新状态dispatch
变更订阅subscribe
zRedux.js
export function createStore(reducer,enhancer){
if(enhancer){
return enhancer(createStore)(reducer)
}
let currentState=undefined,listeners=[];
function getState(){
return currentState
}
function dispatch(action){
currentState=reducer(currentState,action)
listeners.map(cl=>cl())
}
function subscribe(listener){
listeners.push(listener)
}
dispatch({type:'zhaozz'})
return {
getState,
dispatch,
subscribe,
}
}
页面可以用原来的ReduxPage.js测试以上代码
# 中间件实现
核心任务是实现函数序列执行
//把下面加入zRedux.js
export function applyMiddleware(...middlewares) {
// 返回强化以后函数
return createStore=>(...arg)=>{
const store = createStore(...arg)
const midApi={
getState:store.getState,
dispatch:store.dispatch
}
// 使中间件可以获取状态值、派发action
const chain = middlewares.map(mw=>mw(midApi))
// compose可以middlewareChain函数数组合并成⼀个函数
const dispatch = compose(...chain)(store.dispatch)
return{
...store,
dispatch
}
}
}
function compose(...funcs) {
if(funcs.length===0){
return arg=>arg
}else if(funcs.length===1){
return funcs[0]
}
return funcs.reduce((left,right)=>(...args)=>right(left(...args)))
}
logger加入MyReduxStore.js
import { createStore,applyMiddleware } from '../zRedux'
import { counterReducer } from './counterReducer'
const store = createStore(counterReducer,applyMiddleware(logger))
export default store
function logger(dispatch,getState){
return dispatch=>action=>{
console.log(action.type + "done!");
return dispatch(action)
}
}
# redux-thunk原理
thunk增加了处理函数型action的能力,把下面加入MyReduxStore.js
function thunk(dispatch,getState){
return dispatch=>action=>{
if(typeof action === "function") return action(dispatch,getState);
return dispatch(action)
}
}
测试代码
import React, { Component } from 'react';
import store from '../../store/myReduxStore'
class MyReduxPage extends Component {
componentDidMount(){
store.subscribe(()=>{
this.forceUpdate()
// this.setState({})
})
}
render() {
// console.log('MyReduxPage',store);
return (
<div>
<h1>MyReduxPage</h1>
<p>{store.getState()}</p>
<button onClick={()=>{store.dispatch({type:"add"})}}>add</button>
<button onClick={()=>{store.dispatch({type:"minus"})}}>minus</button>
<button onClick={()=>{store.dispatch(dispatch=>{
setTimeout(()=>{
dispatch({type:"add"})
},1000)
})}}>asyncadd</button>
</div>
);
}
}
export default MyReduxPage;
# react-redux原理
# 实现zreact-redux
核心任务:
实现⼀个高阶函数工⼚connect,可以根据传入状态映射规则函数和派发器映射规则函数映射需要的属性,可以处理变更检测和刷新任务
实现⼀个Provider组件可以传递store
import React, { useState, useContext, useEffect } from 'react';
const Context = React.createContext()
export const Provider = props => {
return <Context.Provider value={props.store}>{props.children}</Context.Provider>
}
export const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => {
return Cmp => {
return () => {
const store = useContext(Context)
const getProps = () => {
const stateProps = mapStateToProps(store.getState())
const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
return { ...stateProps, ...dispatchProps }
}
useEffect(() => {
// effect
store.subscribe(() => {
setProps({ ...props, ...getProps() })
})
return () => {
// cleanup
}
}, [])
const [props, setProps] = useState({ ...getProps() })
return <Cmp {...props}></Cmp>
}
}
}
function bindActionCreators(creators, dispatch) {
return Object.keys(creators).reduceRight((ret, item) => {
ret[item] = bindActionCreator(creators[item], dispatch)
return ret
}, {})
}
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args))
}
测试代码
src/App.js
import React from 'react';
import MyReactReduxPage from './pages/my/MyReactReduxPage';
import './App.css';
import { Provider } from "./zreact-redux";
import store from './store/myReactReduxStore'
function App() {
return (
<Provider store={store}>
<div className="App">
<MyReactReduxPage></MyReactReduxPage>
</div>
</Provider>
);
}
export default App;
MyReactReduxPage.js
import React, { Component } from 'react'
import { connect } from '../../zreact-redux'
class MyReactReduxPage extends Component {
render() {
console.log('MyReactReduxPage',this.props);
const {num,add,minus} = this.props
return (
<div>
<h1>MyReactReduxPage</h1>
<p>{num}</p>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
)
}
}
const mapStateToProps = state => {
return {
num: state
}
}
const mapDispatchToProps ={
add: () => {
return {type:'add'}
},
minus: () => {
return {type:'minus'}
},
}
export default connect(mapStateToProps,mapDispatchToProps)(MyReactReduxPage)