深入浅出redux知识
强烈建议使用PC查看此文章,代码跟阅读体验会更好!
https://github.com/wuxianqiang/blog/issues/165
redux状态管理的容器。
开始使用
reducer函数需要判断动作的类型去修改状态,需要注意的是函数必须要有返回值。此函数第一个参数是
state
状态,第二个参数是action
动作,action
参数是个对象,对象里面有一个不为undefined
的type
属性,就是根据这个属性去区分各种动作类型。
在组件中这样使用
const actions = { increment() {return { type: INCREMENT } }, decrement() {return { type: DECREMENT } }}class Counter extends Component {constructor(props) {super(props);// 初始化状态this.state = {num: store.getState().num } } componentDidMount() {// 添加订阅this.unsubscribe = store.subscribe(() => {this.setState({ num: store.getState().num }) }) } componentWillUnmount() {// 取消订阅this.unsubscribe() } increment = () => { store.dispatch(actions.increment()) } decrement = () => { store.dispatch(actions.decrement()) } render() {return (<div><span>{this.state.num}</span><button onClick={this.increment}>加1</button><button onClick={this.decrement}>减1</button></div> ); }}
我们都知道组件中的
state
和props
改变都会导致视图更新,每当容器里面的状态改变需要修改state
,此时就需要用到store
中的subscribe
订阅这个修改状态的方法,该方法的返回值是取消订阅,要修改容器中的状态要用store
中的dispatch
表示派发动作类型,store
中的getState
表示获取容器中的状态。
bindActionCreators
为了防止自己手动调用 store.dispatch
,一般会使用redux的这个 bindActionCreators
方法来自动绑定 dispatch
方法,用法如下。
let actions = { increment() {return { type: INCREMENT } }, decrement() {return { type: DECREMENT } }}
actions = bindActionCreators(actions, store.dispatch)
class Counter extends Component {constructor(props) {super(props);// 初始化状态this.state = {num: store.getState().num } } componentDidMount() {// 添加订阅this.unsubscribe = store.subscribe(() => {this.setState({ num: store.getState().num }) }) } componentWillUnmount() {// 取消订阅this.unsubscribe() } increment = () => { actions.increment() } decrement = () => { actions.decrement() } render() {return (<div><span>{this.state.num}</span><button onClick={this.increment}>加1</button><button onClick={this.decrement}>减1</button></div> ); }}
export default Counter;
react-redux
这个库是连接库,用来和react和redux进行关联的,上面使用redux的时候发现一个痛点就是要订阅设置状态的方法还要取消订阅,而react-redux却可以通过props自动完成这个功能。
import {Provider} from 'react-redux'import {createStore} from 'redux'
const INCREMENT = 'INCREMENT'const DECREMENT = 'DECREMENT'
const initState = { num: 0 }function reducer(state = initState, action) {switch (action.type) {case INCREMENT:return { num: state.num + 1 }case DECREMENT:return { num: state.num - 1 }default:return state }}const store = createStore(reducer)
ReactDOM.render((<Provider store={store}><Counter /></Provider>), document.getElementById('root'))
Provider
是个高阶组件,需要传入store参数作为store属性,高阶组件包裹使用状态的组件。这样就完成了跨组件属性传递。
import {connect} from 'react-redux'const INCREMENT = 'INCREMENT'const DECREMENT = 'DECREMENT'let actions = { increment() {return { type: INCREMENT } }, decrement() {return { type: DECREMENT } }}
class Counter extends Component { render() {return (<div><span>{this.props.num}</span><button onClick={() => this.props.increment()}>加1</button><button onClick={() => this.props.decrement()}>减1</button></div> ); }}const mapStateToProps = state => {return state}const mapDispatchToProps = actions
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
组件中使用
connect
方法关联组件和容器,这个高阶函数,需要执行两次,第一次需要传入两个参数,第一个参数是将状态映射为属性,第二个是将action
映射为属性,第二次需要传入组件作为参数。
mapStateToProps
该参数是个函数返回对象的形式,参数是store中的 state
,可以用来筛选我们需要的属性,防止组件属性太多,难以维护
比如我们状态是这样的{ a: 1, b: 2 }
想改成这样的{ a: 1 }
,使用如下
const mapStateToProps = state => {return { a: state.a }}
mapDispatchToProps
这个方法将action中的方法映射为属性,参数是个函数返回对象的形式,参数是store中的 dispatch
,可以用来筛选action
let actions = {increment() {return { type: INCREMENT } },decrement() {return { type: DECREMENT } }}
现在action
中有两个方法,我们只需要一个的话就可以这么做了。
const mapDispatchToProps = dispatch => {return {increment: (...args) => dispatch(actions.increment(...args)) }}
redux原理
createStore原理
现在你已经掌握了react和react-redux两个库的使用,并且知道他们的作用分别是干什么的,那么我们就看看原理,先学习redux原理,先写一个createStore
方法。
import createStore from './createStore'
export { createStore}
回顾一下createStore
是怎么使用的,使用的时候需要传入一个处理器reducer
函数,根据动作类型修改状态然后返回状态,只有在调用dispatch
方法修改状态的时候才会执行reducer
才能得到新状态。
import isPlainObject from './utils/isPlainObject'import ActionTypes from './utils/actionTypes'
function createStore(reducer, preloadedState) {let currentState = preloadedState
function getState() {return currentState }
function dispatch(action) {// 判断是否是纯对象if (!isPlainObject(action)) {throw new Error('类型错误') }// 计算新状态 currentState = currentReducer(currentState, action) }
dispatch({ type: ActionTypes.INIT })
return { dispatch, getState }}
export default createStore
在调用 dispatch
方法的时候,需要传入一个对象,并且有个 type
属性,为了保证传入的参数的正确性,调用了isPlainObject
方法,判断是否是一个对象。
function isPlainObject (obj) {if (typeof obj !== 'object' || obj === null) {return false }let proto = objwhile (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) }return Object.getPrototypeOf(obj) === proto}export default isPlainObject
该方法的原理就是判断这个对象的原型和 Object.prototype
是否相等。
redux中还有订阅和取消订阅的方法,每当状态改变执行订阅的函数。发布订阅是我们再熟悉不过的原理了,我就不多说了。
function createStore(reducer, preloadedState) {let currentState = preloadedStatelet currentReducer = reducerlet currentListeners = []let nextListeners = currentListeners
// 拷贝function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
function getState() {return currentState }function subscribe(listener) {if (typeof listener !== 'function') {throw new Error('类型错误') }// 订阅 ensureCanMutateNextListeners() nextListeners.push(listener)return function unsubscribe() { ensureCanMutateNextListeners()const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }function dispatch(action) {// 判断是否是纯对象if (!isPlainObject(action)) {throw new Error('类型错误') }// 计算新状态 currentState = currentReducer(currentState, action)// 发布const listeners = (currentListeners = nextListeners)for (let i = 0; i < listeners.length; i++) {const listener = listeners[i] listener() } } dispatch({ type: ActionTypes.INIT })return { dispatch, getState, subscribe }}
ensureCanMutateNextListeners
的作用是,如果是在 listeners
被调用期间发生订阅(subscribe)或者解除订阅(unsubscribe),在本次通知中并不会立即生效,而是在下次中生效。
代码里面有个值得注意的是调用了一次dispatch
方法,派发一次动作,目的是为了得到默认值,而且为了这个动作类型与众不同,防止定义的类型冲突,所以redux这么来写。
const randomString = () => Math.random().toString(36).substring(7).split('').join('.')
const ActionTypes = {INIT: `@@redux/INIT${randomString()}`}
export default ActionTypes
bindActionCreators原理
bindActionCreators
在上面已经介绍了他的作用,就是为每个方法自动绑定dispatch
方法。
export default function bindActionCreators(actionCreators, dispatch) {function bindActionCreator(actionCreators, dispatch) {return function () {return dispatch(actionCreators.apply(this, arguments)) } }if (typeof actionCreators === 'function') { bindActionCreator(actionCreators, dispatch) }const boundActionCreator = {}for (const key in actionCreators) { boundActionCreator[key] = bindActionCreator(actionCreators[key], dispatch) }return boundActionCreator}
完
深入浅出redux知识相关推荐
- 【知识图谱】深入浅出讲解知识图谱(技术、构建、应用)
本文收录于<深入浅出讲解自然语言处理>专栏,此专栏聚焦于自然语言处理领域的各大经典算法,将持续更新,欢迎大家订阅! 个人主页:有梦想的程序星空 个人介绍:小编是人工智能领域硕士,全栈工程师 ...
- React Native 学习资源精选仓库(汇聚知识,分享精华)
React Native 学习资源精选仓库(汇聚知识,分享精华) <React Native Awesome>这里fork过来的,汇集了各类react-native学习资料.工具.组件.开 ...
- 全面访问JavaScript的最佳资源
Looking for a new job is a daunting task. There are so many things to consider when trying to find t ...
- 面对互联网一线大厂,这些技术你需要了解!
2019 年 5 月 26 - 27 日,由中国 IT 社区 CSDN 与数字经济人才发展中心联合主办的第一届 CTA 核心技术及应用峰会将在杭州国际博览中心召开.近 500 名开发者将齐聚于此,共同 ...
- 神策数据罗彦博:如何正确使用漏斗分析提升转化?
本文根据神策数据数字营销经理罗彦博<如何正确使用漏斗分析提升转化>直播整理而成.本文主要内容如下: 通过漏斗分析做 SEM 实时调整 推广落地页的转化路径设计 APP 推广中的数据拦截问题 ...
- 全球首届“AI球球大作战:Go-Bigger多智能体决策智能挑战赛”开启
<球球大作战>是一款风靡全球的休闲电子竞技游戏,以大球吃小球为目标,简单有趣却又斗智斗勇. 你不知道的是,AI世界也拥有了自己的<球球大作战>. 前不久,OpenDILab开源 ...
- 无线通信AI大赛正式开放评测,50万大奖等你霸榜!
权威赛事 大咖群集 高额奖金 共计50万 简单赛制 一榜到底 无线通信AI大赛.就等你来! 一 大赛介绍 科技是历史前进的推动力,是国家竞争的制高点.中国突破经济发展中的瓶颈.实现经济发展转型的根本出 ...
- (七)React使用
React使用 React vs Vue React使用 React面试题 React基本使用 JSX基本使用 条件判断 渲染列表 事件 表单 组件使用 setState 组件生命周期 单组件生命周期 ...
- 为什么你的年薪只是别人的月薪?你需要技术专家帮你「充电」
戳蓝字"CSDN云计算"关注我们哦! 2019 年 5 月 26 - 27 日,由中国 IT 社区 CSDN 与数字经济人才发展中心联合主办的第一届 CTA核心技术及应用峰会将在杭 ...
最新文章
- 云计算 - OpenStack
- asp.net 备份和恢复SQL SERVER 数据库
- LA 2402 (枚举) Fishnet
- php下载https图片,php下载https图片报错Failed to enable crypto
- java护照号码校验_SpringBoot如何优雅的校验参数
- gpedit msc组策略面板 win10在哪里_Win10系统gpedit.msc在哪?Win10系统gpedit.msc组策略打不开怎么办?...
- vue+sortable实现表格拖拽
- 并发编程(六)并发容器
- 物理机安装linux系统,物理机安装linux的三种方法
- java导出简单写法
- C# 获取所有网卡信息
- FLASHFTP简体中文破解版
- 西南科技大学OJ题 最简单的C程序0612
- 单片机:Keil的安装教程
- 高校学籍管理系统(SQL Server数据库课程设计)
- 【01月11日】【精彩电影合集】【10部】【亲测】【Lsyq5647发布】
- cd linux给u盘安装程序,CDLINUX U盘安装教程
- meta http-equiv=refresh content=0; url=是什么意思
- 【论文阅读】DNS隧道攻击检测算法整合
- IC 短缺的影响超出汽车领域
热门文章
- 全国第一条5G步行街开街;罗永浩回应直播有多赚钱:没那么夸张;Windows Terminal 1.4发布|极客头条
- 超星未来张剑谈智能网联汽车计算平台:不能只考虑算力
- 2020 美国大选在即,又到了 AI 花式打击假新闻的季节
- 萌新程序员找工作该怎么写简历?
- 10 倍高清不花!大麦端选座 SVG 渲染
- 学编程不如学自动化?
- 为什么我选择用 C 编写游戏?
- 程序员为什么要懂物联网?
- Android 10 重磅来袭:支持 5G 与折叠屏、隐私安全全面升级!
- 树莓派 4 与英伟达 Jetson Nano 性能大比拼,谁是最佳的嵌入式“电脑”?