深入浅出redux知识

react 专栏收录该内容
15 篇文章 2 订阅

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)

reducer函数需要判断动作的类型去修改状态,需要注意的是函数必须要有返回值。此函数第一个参数是 state 状态,第二个参数是 action 动作,action 参数是个对象,对象里面有一个不为 undefinedtype 属性,就是根据这个属性去区分各种动作类型。

在组件中这样使用

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>
    );
  }
}

我们都知道组件中的 stateprops 改变都会导致视图更新,每当容器里面的状态改变需要修改 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 = obj
  while (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 = preloadedState
  let currentReducer = reducer
  let 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
}
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
<p style="text-align:left;"> <span> </span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-family:"color:#E53333;font-size:14px;background-color:#FFFFFF;line-height:24px;"><span style="line-height:24px;">限时福利1:</span></span><span style="font-family:"color:#3A4151;font-size:14px;background-color:#FFFFFF;">购课进答疑群专享柳峰(刘运强)老师答疑服务。</span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <strong><span style="color:#337FE5;font-size:14px;">为什么说每一个程序员都应该学习MySQL?</span></strong> </p> <p class="ql-long-24357476"> <span style="font-size:14px;">根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。</span> </p> <p class="ql-long-24357476"> <img src="https://img-bss.csdn.net/202003301212574051.png" alt="" /> </p> <p class="ql-long-24357476"> <span style="font-size:14px;">使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。</span><br /> <br /> <span style="font-size:14px;">学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能!</span> </p> <span></span> <p> <br /> </p> <p> <span> </span> </p> <h3 class="ql-long-26664262"> <p style="font-size:12pt;"> <strong class="ql-author-26664262 ql-size-14"><span style="font-size:14px;color:#337FE5;">【课程设计】</span></strong> </p> <p style="font-size:12pt;"> <span style="color:#494949;font-weight:normal;"><br /> </span> </p> <p style="font-size:12pt;"> <span style="color:#494949;font-weight:normal;font-size:14px;">在本课程中,刘运强老师会结合自己十多年来对MySQL的心得体会,通过课程给你分享一条高效的MySQL入门捷径,让学员少走弯路,彻底搞懂MySQL。</span> </p> <p style="font-size:12pt;"> <span style="color:#494949;font-weight:normal;"><br /> </span> </p> <p style="font-size:12pt;"> <span style="font-weight:normal;font-size:14px;">本课程包含3大模块:</span><span style="font-weight:normal;font-size:14px;"> </span> </p> </h3> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <strong class="ql-author-26664262"><span style="font-size:14px;">一、基础篇:</span></strong> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span class="ql-author-26664262" style="font-size:14px;">主要以最新的MySQL8.0安装为例帮助学员解决安装与配置MySQL的问题,并对MySQL8.0的新特性做一定介绍,为后续的课程展开做好环境部署。</span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span class="ql-author-26664262" style="font-size:14px;"><br /> </span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <strong class="ql-author-26664262"><span style="font-size:14px;">二、SQL语言篇</span></strong><span class="ql-author-26664262" style="font-size:14px;">:</span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span class="ql-author-26664262" style="font-size:14px;">本篇主要讲解SQL语言的四大部分数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL,</span><span style="font-size:14px;">学会熟练对库表进行增删改查等必备技能。</span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;"><br /> </span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <strong class="ql-author-26664262"><span style="font-size:14px;">三、MySQL进阶篇</span></strong><span style="font-size:14px;">:</span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">本篇可以帮助学员更加高效的管理线上的MySQL数据库;具备MySQL的日常运维能力,语句调优、备份恢复等思路。</span> </p> <span><span> <p style="font-size:11pt;color:#494949;"> <span style="font-size:14px;"> </span><img src="https://img-bss.csdn.net/202004220208351273.png" alt="" /> </p> </span></span>
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页

打赏

前端精髓

小礼物走一走,来CSDN关注我

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值