百度做任务的网站,网站搭建费用明细,烟台建设科技网站,小蓝鸟pnv加速器本文来自#React系列教程#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNAactiongetalbumalbum_id1566025152667107329) 在React的开发过程中#xff0c;Redux对于我们是非常重要的。 但是对于很多人来说#xff0c;初次接触redux会感觉r… 本文来自#React系列教程https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNAactiongetalbumalbum_id1566025152667107329) 在React的开发过程中Redux对于我们是非常重要的。 但是对于很多人来说初次接触redux会感觉redux的学习坡度非常陡峭难度比较大。 在接下来我会通过一个系列的方式循循渐进、层层深入的方式来讲解Redux从redux的独立使用过程、到redux的结构划分、到redux结合react的过程、到redux中异步操作、到复杂数据的结构处理、到结合immutable的使用。 整个过程都是在现有知识的基础之上层层递增、一步步掌握redux的使用 一. 认识Redux
1.1. JavaScript纯函数
函数式编程中有一个概念叫纯函数JavaScript符合函数式编程的范式所以也有纯函数的概念
在React中纯函数的概念非常重要在接下来我们学习的Redux中也非常重要所以我们有必须来回顾如果你之前没有学过那么你就是学习一下纯函数。
纯函数的维基百科定义
在程序设计中若一个函数符合以下条件那么这个函数被称为纯函数
此函数在相同的输入值时需产生相同的输出。函数的输出和输入值以外的其他隐藏信息或状态无关也和由I/O设备产生的外部输出无关。该函数不能有语义上可观察的函数副作用诸如“触发事件”使输出设备输出或更改输出值以外物件的内容等。
当然上面的定义会过于的晦涩所以我简单总结一下
确定的输入一定会产生确定的输出函数在执行过程中不能产生副作用
那么我们来看几个函数是否是纯函数
案例一
function sum(num1, num2) {return num1 num2;
}很明显上面的函数是一个纯函数它的输出是依赖我们的输入内容并且中间没有产生任何副作用
案例二
let foo 5;function add(num) {return foo num;
}console.log(add(5));
foo 10;
console.log(add(5));add函数不是一个纯函数函数依赖一个外部的变量变量发生改变时会影响确定的输入产生确定的输出能否改进成纯函数呢const foo 5; 即可
案例三
function printInfo(info) {console.log(info.name, info.age);info.name 哈哈哈;
}printInfo不是一个纯函数虽然无论输入什么最终输出都是undefined但是它产生了副作用修改了传入的对象
当然纯函数还有很多的变种但是我们只需要理解它的核心就可以了。
为什么纯函数在函数式编程中非常重要呢
因为你可以安心的写和安心的用你在写的时候保证了函数的纯度只是实现自己的业务逻辑即可不需要关心传入的内容或者依赖其他的外部变量你在用的时候你确定你的输入内容不会被任意篡改并且自己确定的输入一定会有确定的输出
React 非常灵活但它也有一个严格的规则
所有的 React 组件无论是函数组件还是class声明的组件都必须像纯函数一样保护它们的props不被修改。
在之后学习redux中reducer也被要求是一个纯函数。
1.2. 认识Redux
1.2.1. 为什么需要redux
JavaScript开发的应用程序已经变得越来越复杂了
JavaScript需要管理的状态越来越多越来越复杂这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等也包括一些UI的状态比如某些元素是否被选中是否显示加载动效当前分页
管理不断变化的state是非常困难的
状态之间相互会存在依赖一个状态的变化会引起另一个状态的变化View页面也有可能会引起状态的变化当应用程序复杂时state在什么时候因为什么原因而发生了变化发生了怎么样的变化会变得非常难以控制和追踪
React是在视图层帮助我们解决了DOM的渲染过程但是State依然是留给我们自己来管理
无论是组件定义自己的state还是组件之间的通信通过props进行传递也包括通过Context进行数据之间的共享React主要负责帮助我们管理视图state如何维护最终还是我们自己来决定 Redux就是一个帮助我们管理State的容器Redux是JavaScript的状态容器提供了可预测的状态管理
Redux除了和React一起使用之外它也可以和其他界面库一起来使用比如Vue并且它非常小包括依赖在内只有2kb
1.2.2. redux的核心理念
Redux的核心理念非常简单。
比如我们有一个朋友列表需要管理
const initialState {friends: [{ name: why, age: 18 },{ name: kobe, age: 40 },{ name: lilei, age: 30 },]
};如果我们没有定义统一的规范来操作这段数据那么整个数据的变化就是无法跟踪的
比如页面的某处通过friends.push的方式增加了一条数据比如另一个页面通过friends[0].age 25修改了一条数据整个应用程序错综复杂当出现bug时很难跟踪到底哪里发生的变化
Redux要求我们通过action来更新数据
所有数据的变化必须通过派发dispatchaction来更新action是一个普通的JavaScript对象用来描述这次更新的type和content
比如下面就是几个更新friends的action
const action1 { type: ADD_FRIEND, info: { name: lucy, age: 20 } }
const action2 { type: INC_AGE, index: 0 }
const action3 { type: CHANGE_NAME, playload: { index: 0, newName: coderwhy } }强制使用action的好处是可以清晰的知道数据到底发生了什么样的变化所有的数据变化都是可跟追、可预测的当然目前我们的action是固定的对象真实应用中我们会通过函数来定义返回一个action
但是如何将state和action联系在一起呢答案就是reducer
reducer是一个纯函数reducer做的事情就是将传入的state和action结合起来生成一个新的state
function reducer(state initialState, action) {switch (action.type) {case ADD_FRIEND:return { ...state, friends: [...state.friends, action.info] }case INC_AGE:return {...state, friends: state.friends.map((item, index) {if (index action.index) {return { ...item, age: item.age 1 }}return item;})}case CHANGE_NAME:return {...state, friends: state.friends.map((item, index) {if (index action.index) {return { ...item, name: action.newName }}return item;})}default:return state;}
}这个reducer有点像 Android 的 Handler 的 handleMessage() 方法中根据不同的消息命令进行分发处理的逻辑。
1.2.3. redux的三大原则
1. 单一数据源
整个应用程序的state被存储在一颗object tree中并且这个object tree只存储在一个 store 中
Redux并没有强制让我们不能创建多个Store但是那样做并不利于数据的维护单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改
2. State是只读的
唯一修改State的方法一定是触发action不要试图在其他地方通过任何的方式来修改State
这样就确保了View或网络请求都不能直接修改state它们只能通过action来描述自己想要如何修改state这样可以保证所有的修改都被集中化处理并且按照严格的顺序来执行所以不需要担心race condition竟态的问题
3. 使用纯函数来执行修改
通过 reducer 将 旧state 和 actions联系在一起并且返回一个新的State
随着应用程序的复杂度增加我们可以将reducer拆分成多个小的reducers分别操作不同state tree的一部分但是所有的reducer都应该是纯函数不能产生任何的副作用
二. redux的基本使用
2.1. redux使用过程
安装redux
npm install redux --save
# 或
yarn add redux这里我通过创建一个简单的js文件我们先来简单学习一下redux
搭建项目结构
创建一个新的项目文件夹learn-redux
# 执行初始化操作
yarn init# 安装redux
yarn add redux创建src目录并且创建index.js文件 修改package.json可以执行index.js
scripts: {start: node src/index.js
}开始在index.js中编写代码
创建一个对象作为我们要保存的状态
const redux require(redux);const initialState {counter: 0
}创建Store来存储这个state
创建store时必须创建reducer我们可以通过 store.getState 来获取当前的state
// 创建reducer
const reducer (state initialState, action) {return state;
}// 根据reducer创建store
const store redux.createStore(reducer);
console.log(store.getState());通过action来修改state
通过dispatch来派发action通常action中都会有type属性也可以携带其他的数据
store.dispatch({type: INCREMENT
})store.dispath({type: DECREMENT
})store.dispatch({type: ADD_NUMBER,number: 5
})修改reducer中的处理代码
这里一定要记住reducer是一个纯函数不需要直接修改state后面我会讲到直接修改state带来的问题
const 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.number}default: return state;}
}可以在派发action之前监听store的变化
store.subscribe(() {console.log(store.getState());
})完成的案例代码如下
const redux require(redux);const initialState {counter: 0
}// 创建reducer
const 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.number}default: return state;}
}// 根据reducer创建store
const store redux.createStore(reducer);store.subscribe(() {console.log(store.getState());
})// 修改store中的state
store.dispatch({type: INCREMENT
})
// console.log(store.getState());store.dispatch({type: DECREMENT
})
// console.log(store.getState());store.dispatch({type: ADD_NUMBER,number: 5
})
// console.log(store.getState());2.2. redux结构划分
如果我们将所有的逻辑代码写到一起那么当redux变得复杂时代码就难以维护。
接下来我会对代码进行拆分将store、reducer、action、constants拆分成一个个文件。
注意node中对ES6模块化的支持
目前我使用的node版本是v12.16.1从node v13.2.0开始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后缀名
对redux结构进行划分
创建store/index.js文件
import redux from redux;
import reducer from ./reducer.js;const store redux.createStore(reducer);export default store;创建store/reducer.js文件
import {ADD_NUMBER,SUB_NUMBER
} from ./constants.js;const initialState {counter: 0
}function reducer(state initialState, action) {switch(action.type) {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;创建store/actionCreators.js文件
import {ADD_NUMBER,SUB_NUMBER
} from ./constants.jsconst addAction (count) ({type: ADD_NUMBER,num: count
});const subAction (count) ({type: SUB_NUMBER,num: count
})export {addAction,subAction
}创建store/constants.js文件
const ADD_NUMBER ADD_NUMBER;
const SUB_NUMBER SUB_NUMER;export {ADD_NUMBER,SUB_NUMBER
}2.3. Redux流程图
我们已经知道了redux的基本使用过程那么我们就更加清晰来认识一下redux在实际开发中的流程
全局通常只有一个Store存储我们的StateComponent中在某些情况会派发Action这些Action是我们提前定义好的Reducer会接收到这些Action并且在Reducer中会返回一个新的State作为Store的StateState发生更新之后会触发通知告知订阅者数据发生了改变订阅者拿到最新的数据在props中更新到jsx中界面发生改变 三. react结合redux
3.1. redux融入react代码
目前redux在react中使用是最大的所以我们需要将之前编写的redux代码融入到react当中去。
这里我创建了两个组件
Home组件其中会展示当前的counter值并且有一个1和5的按钮Profile组件其中会展示当前的counter值并且有一个-1和-5的按钮
home.js代码实现
import React, { PureComponent } from react;import store from ../store;
import {addAction
} from ../store/actionCreators;export default class Home extends PureComponent {constructor(props) {super(props);this.state {counter: store.getState().counter}}componentDidMount() {store.subscribe(() {this.setState({counter: store.getState().counter})})}render() {return (divh1Home/h1h2当前计数: {this.state.counter}/h2button onClick{e this.increment()}1/buttonbutton onClick{e this.addCounter()}5/button/div)}increment() {store.dispatch(addAction(1));}addCounter() {store.dispatch(addAction(5));}
}Profile.js代码实现
import React, { PureComponent } from react;import store from ../store;
import {subAction
} from ../store/actionCreators;export default class Profile extends PureComponent {constructor(props) {super(props);this.state {counter: store.getState().counter}}componentDidMount() {store.subscribe(() {this.setState({counter: store.getState().counter})})}render() {return (divhr/h1Profile/h1divh2当前计数: {this.state.counter}/h2button onClick{e this.decrement()}-1/buttonbutton onClick{e this.subCounter()}-5/button/div/div)}decrement() {store.dispatch(subAction(1));}subCounter() {store.dispatch(subAction(5));}
}上面的代码其实非常简单核心代码主要是两个
在 componentDidMount 中定义数据的变化当数据发生变化时重新设置 counter;在发生点击事件时调用store的dispatch来派发对应的action
不要忘记取消订阅 3.2. 自定义 connect 函数
上面的代码是否可以实现react组件和redux结合起来呢
当然是可以的但是我们会发现每个使用的地方其实会有一些重复的代码比如监听store数据改变的代码都需要在 componentDidMount中完成比如派发事件我们都需要去先拿到 store 在调用其 dispatch 等
能否将这些公共的内容提取出来呢
我们来定义一个connect函数
这个connect函数本身接受两个参数
参数一里面存放 component 希望使用到的 State 属性参数二里面存放 component 希望使用到的 dispatch动作
这个connect函数有一个返回值是一个高阶组件
在constructor中的state中保存一下我们需要获取的状态在componentDidMount中订阅store中数据的变化并且执行 setState操作在componentWillUnmount中需要取消订阅在render函数中返回传入的WrappedComponent并且将所有的状态映射到其props中这个高阶组件接受一个组件作为参数返回一个class组件在这个class组件中我们进行如下操作
import React, { PureComponent } from react;import store from ../store;export default function connect(mapStateToProps, mapDispatchToProps) {return function handleMapCpn(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())}{...mapDispatchToProps(store.dispatch)}/}}}
}在home和props文件中我们按照自己需要的state、dispatch来进行映射
比如home.js中进行如下修改
const mapStateToProps state {return {counter: state.counter}
}const mapDispatchToProps dispatch {return {addNumber: function(number) {dispatch(addAction(number));}}
}export default connect(mapStateToProps, mapDispatchToProps)(Home);mapStateToProps用于将state映射到一个对象中对象中包含我们需要的属性mapDispatchToProps用于将dispatch映射到对象中对象中包含在组件中可能操作的函数当调用该函数时本质上其实是调用dispatch(对应的Action)
在Profile中也是类似的操作。
有了connect函数我们之后只需要关心从state和dispatch中映射自己需要的状态和行为即可
3.3. store的context处理
但是上面的connect函数有一个很大的缺陷依赖导入的store
如果我们将其封装成一个独立的库需要依赖用于创建的store我们应该如何去获取呢 难道让用户来修改我们的源码吗不太现实
正确的做法是我们提供一个ProviderProvider来自于我们创建的Context让用户将store传入到value中即可
创建一个context.js文件
import { createContext } from react;export const StoreContext createContext();修改connect函数中class组件部分的代码
注意下面我们将class组件的名称明确的定义出来并且给它的contextType进行了赋值在组件内部用到store的地方统一使用this.context代替注意constructor中直接使用第二个参数即可
import React, { PureComponent } from react;import { StoreContext } from ./context;export default function connect(mapStateToProps, mapDispatchToProps) {return function handleMapCpn(WrappedComponent) {class ConnectCpn extends PureComponent {constructor(props, context) {super(props);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())}{...mapDispatchToProps(this.context.dispatch)} /}}ConnectCpn.contextType StoreContext;return ConnectCpn;}
}在入口的index.js中使用Provider并且提供store即可
import { StoreContext } from ./utils/context;
import store from ./store;ReactDOM.render(StoreContext.Provider value{store}App //StoreContext.Provider,document.getElementById(root)
);测试代码依然可以正常运行。
四. react-redux使用
4.1. react-redux的使用
开始之前需要强调一下redux和react没有直接的关系你完全可以在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux。
尽管这样说redux依然是和React或者Deku的库结合的更好因为他们是通过state函数来描述界面的状态Redux可以发射状态的更新让他们作出相应。
虽然我们之前已经实现了connect、Provider这些帮助我们完成连接redux、react的辅助工具但是实际上redux官方帮助我们提供了 react-redux 的库可以直接在项目中使用并且实现的逻辑会更加的严谨和高效。
安装react-redux
yarn add react-redux使用connect函数
将之前使用的connect函数换成react-redux的connect函数
import React, { PureComponent } from react;
import { connect } from react-redux;// import connect from ../utils/connect2;export default connect(mapStateToProps, mapDispatchToProps)(Home);使用Provider
将之前自己创建的Context的Provider换成react-redux的Provider组件注意这里传入的是store属性而不是value属性待会儿可以在源码中查看
import { Provider } from react-redux;import store from ./store;ReactDOM.render(Provider store{store}App //Provider,document.getElementById(root)
);2.2. react-redux的源码
这里我简单带着大家看一下react-redux的源码
但是第一因为这个教程不是讲源码为主的教程穿插讲解部分源码所以源码只会阅读核心的部分另外我经常会说整个社区在hooks出现后大量的库转向了hooks所以在源码中会出现大量的hooks代码因为目前还没有讲解hooks相关的API所以某些hooks的作用在这里也不方便解释可以学习完hooks之后再详细阅读
首先我们简单看一下Provider的源码 使用了一个useMemo来返回一个contextValue的对象这里使用useMemo的原因是为了进行性能的优化在依赖的store不改变的情况下不会进行重新计算返回一个新的对象在下面的Context的Provider中就会将其赋值给value属性
ReactReduxContext来自另外一个文件 connect函数的依赖比较复杂
调用createConnect来返回一个connect函数 createConnect函数的调用 connect函数最终调用的是connectHOC
connectHOC其实是connectAdvanced的函数connectAdvanced函数最终返回的是wrapWithConnect函数 wrapWithConnect函数