Skip to content

Redux其实很简单(示例篇) #29

@ansenhuang

Description

@ansenhuang

记得在16年初识Redux时,笔者是被其搞得晕头转向,总觉得有点把简单的事情复杂化,而且对于Action、Reducer等,它们是怎样连接state并触发视图更新的,也是一脸懵逼。

近来工作没有那么忙,饶有兴致地抽时间来研究了一下Redux的源码,希望可以帮助大家弄懂Redux原理,以便更加轻松地投入到实际业务开发中。

本系列文章一共分为两篇,阅读完本系列文章,你将会对Redux的思想和使用有深刻的理解。示例篇将通过一个Redux示例页面来帮助大家了解其使用流程,帮助大家先理解Redux的思想,为后续自己动手写一个Redux打下基础;原理篇将会带大家一步一步实现一个Redux,同时还会附上详细的讲解以及笔者个人的一些思考。

如果已经熟悉Redux的使用,可以直接跳过本文,阅读 Redux其实很简单(原理篇)

使用前提

Redux是一个状态管理容器,在很多情况下,我们并不需要它。但是假如在页面中,有许多状态数据需要在不同组件中共享,状态管理非常困难时,可以考虑使用Redux,它可以帮助我们组织代码结构,方便进行状态管理。通常情况下spa页面比较常用,搭配前端路由管理一起使用。

示例页面

store

核心方法:createStore(reducer, [initialState], enhancer)

// index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from '@/common/redux'
import logger from '@/common/redux-logger'
import thunk from '@/common/redux-thunk'

import reducers from './reducers'
import Counter from './components/Counter'

const store = createStore(reducers, { counter: 0 }, applyMiddleware(thunk, logger))
const renderWithStore = () => render(
  <Counter
    data={store.getState()}
    dispatch={store.dispatch}
  />,
  document.getElementById('root')
)

renderWithStore()
store.subscribe(renderWithStore)

首先我们通过核心方法createStore得到渲染视图必要的API【getState, dispatch, subscribe】,再将渲染函数注册到监听流当中,每当dispatch触发时,所有注册到监听流的函数都会执行,这也是为什么状态改变时视图也会更新的关键。

举个例子,上述代码中最后再加一行

store.subscribe(() => {
  console.info('test')
})

那么dispatch触发时,除了视图重新渲染,还会再控制台打印出test。

reducers

// reducers/counter.js
export default (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    case 'INCREMENT_BY':
      return state + action.count
    default:
      return state
  }
}

然后我们通过combineReducers将多个reducer合并成一个,此时state是一个对象,每一个reducer的状态对应state的一个键值。

// reducers/index.js
import { combineReducers } from '@/common/redux'
import counter from './counter'

export default combineReducers({
  counter
})

到目前为止,我们已经将状态和视图通过reducer连接起来了。接下来我们将通过action来触发dispatch,在这个过程中,dispatch会触发reducer来得到最新的state,然后执行监听流当中的函数来重新渲染视图。

action

// actions/counter.js
export function increment () {
  return {
    type: 'INCREMENT'
  }
}

export function decrement () {
  return {
    type: 'DECREMENT'
  }
}

export function incrementAsync () {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment())
    }, 1000)
  }
}

export function incrementBy (count = 0) {
  return {
    type: 'INCREMENT_BY',
    count
  }
}
// actions/index.js
import * as counterCreator from './counter'

export {
  counterCreator
}

action定义好了之后,我们就可以在视图上触发action来改变state的值,更新视图。我们通过action.type来匹配reducer中对应的类型来得到新的state值,值得注意的是,原始的Redux只支持返回一个包含type字段的对象,通过redux-thunk中间件,我们可以进行异步操作,比如说请求接口,拿到数据后触发dispatch。

component

// components/Counter.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { bindActionCreators } from '@/common/redux'
import { counterCreator } from '../actions'

export default class Counter extends Component {
  constructor (props) {
    super(props)

    this.boundActionCreators = bindActionCreators(counterCreator, props.dispatch)
  }

  static propTypes = {
    data: PropTypes.object.isRequired,
    dispatch: PropTypes.func.isRequired
  }

  increment () {
    this.boundActionCreators.increment()
  }

  decrement () {
    this.boundActionCreators.decrement()
  }

  incrementAsync () {
    this.boundActionCreators.incrementAsync()
  }

  incrementBy () {
    this.boundActionCreators.incrementBy(10)
  }

  incrementIfOdd () {
    if (this.props.data.value1 % 2 !== 0) {
      this.boundActionCreators.increment()
    }
  }

  render () {
    const { data } = this.props

    return (
      <div>
        <p>
          Counter: {data.counter} times
          {' '}
          <button onClick={this.increment.bind(this)}>
            +
          </button>
          {' '}
          <button onClick={this.decrement.bind(this)}>
            -
          </button>
          {' '}
          <button onClick={this.incrementIfOdd.bind(this)}>
            Increment if odd
          </button>
          {' '}
          <button onClick={this.incrementAsync.bind(this)}>
            Increment async
          </button>
          {' '}
          <button onClick={this.incrementBy.bind(this)}>
            Increment by
          </button>
        </p>
      </div>
    )
  }
}

我们通过bindActionCreators将dispatch和action绑定在一起,然后直接调用就可以了。当然也可以不用绑定,那就这样写:

this.props.dispatch(counterCreator.increment())

bindActionCreators的作用就是省略这一步,将两者系在一起直接调用。

文末总结

至此,一个完整的示例页面已经编写完成。在写的过程中,笔者已经将各个API的作用及其联系做了简单的说明,方便大家了解其执行流程和思想,那么在接下来的一篇文章中,笔者会带大家一步一步编写一个Redux,相信大家看完之后会对Redux有一个深刻的认识。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions