4

摘至: please call me HR

redux 是fb提出的一个面向所有库或者框架的MVVM的一种思维. 相当于将react 专有的flux 提取出来。 作为一个专业库来做。这样的相当于使用react+flux构造的MVVM, 可以使用原生js + redux来写, 限制性减小了很多.
由于是借用flux的, 所以很多原理和flux 很相似. 官方提出的有三点:

  • Single source of truth: 单一数据源,其实就是一个Obj或者Array

  • State is read-only: 不能手动改变数据源

  • Changes are made with pure functions: 改变数据源,只能通过reducer来改变

redux和flux的差别

说实话, 两者看底层代码是完全不一样的. 但宏观的基本原理来说,并没有多大差别.
这是flux的:

flux

这是redux:

redux

觉得并没有什么太大的区别.
接下来, 我们来细致的看一看redux到底有什么用.

入门redux

我们这里利用redux来简单写一个小组件。这里,我们不依赖任何第三方库,自己手动写.
首先HTML内容为:

 <div>
      <p>
        Clicked: <span id="value">0</span> times
        <button id="increment">+</button>
        <button id="decrement">-</button>
        <button id="incrementIfOdd">Increment if odd</button>
        <button id="incrementAsync">Increment async</button>
      </p>
    </div>

接着,就是redux内容:

// 先写一个Reducer
function counter(state, action) {
  if (typeof state === 'undefined') {
    return 0
  }

  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}
// 并注册该Reducer
var store = Redux.createStore(counter)

由于没有使用react, 这里的render方法,需要我们by hand进行手写.

// 改变节点的textNode内容
var valueEl = document.getElementById('value')
function render() {
    valueEl.innerHTML = store.getState().toString()
}
render()
// 在store里面,绑定render渲染方法. 但改变store的时候,会触发一次render
store.subscribe(render)

最后,就是书写一下Event即可

 document.getElementById('increment').addEventListener('click', function () {
        // 绑定Action, 触发dispatch操作
        store.dispatch({ type: 'INCREMENT' })
})
// 后面还有一些绑定方法,我这里就不写了.

整个代码是在Counter里. 根据上面的demo,我们可以大概的了解到. store是redux的core part. 一共有三个基本的方法, 搭建起redux的骨架.

  • Redux.createStore(Reducer): 创建store对象

  • store.subscribe(render): 绑定渲染方法

  • store.dispatch({type}): 触发Reducer,进行渲染.

这里,顺便说一下redux中Store,Reducer,Action的意义:

  • Store: 单一数据源, 用来存放数据,并且和DOM同步

  • Reducer: 注册对应Action的函数,用来显示改变store

  • Action: 触发dispatch的行为. 比如: Event 绑定内容

上面介绍的是简单使用redux进行纯开发. 接着,我们来简单看如何结合react进行MVVM开发.

react+redux

我们都知道,react里面有一个最特殊的属性就是state. 他通过自身state的改变,来导致重新渲染发生.this.setState({xxx:xxx}). 但是 redux里面也有一个store, 当然,一山不能容二虎. 那只能二者选其一(当然选redux). 那这样的话,我们就不能使用this.setState进行手动更新. 那么就需要考虑能否使用redux进行代替.
方法是有的,即,使用上面替代的store.subscribe(render).通过props将dispatch的方法传入,进行Reducer的更改.
PS: 这里主要参考todos里的代码

easy react+redux demo

先来看一下,在渲染层中的App内容.

const store = createStore(counter)
const rootEl = document.getElementById('root')

// 将dispatch的函数通过props向子组件进行传递
function render() {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
      onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
    />,
    rootEl  
  )
}
render()
// 将render 缓存,以备下一次进行刷新.
store.subscribe(render)

接着在组件中,调用该方法:

  render() {
  // 获取props里面,传递来的参数
    const { value, onIncrement, onDecrement } = this.props
    return (
      <p>
        Clicked: {value} times
        {' '}
        <button onClick={onIncrement}>
          +
        </button>
        {' '}
        <button onClick={onDecrement}>
          -
        </button>
        {' '}
        <button onClick={this.incrementIfOdd}>
          Increment if odd
        </button>
        {' '}
        <button onClick={this.incrementAsync}>
          Increment async
        </button>
      </p>
    )
  }

reducer内容还是一样:

export default function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

当react和redux结合在一起时, react中,内部状态管理的机制,就需要显示的交给redux来做.

reducer 讲解

reducer 就是一个函数,关键在里面接受的参数

// 第一个参数是 createStore内部解决的. 第二个参数action是你通过dispatch({})中传递的Obj.
// like: dispatch({type: 'INCREMENT'})
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

redux + react高阶应用

上面那个简单的demo,我们意会一下就ok了. 实际生产环境中,并不是这样写代码的. 因为在结合react的时候,必定会涉及到多个component. 但,由于结合redux时,react把state交给redux来管理, 为了满足这个需求,redux提出了container的概念.

redux container

我们参考todos的源码, 来细致的看一下redux 高阶应用.

首先看他的index.js:

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
// 其余的剩余...

// 将Reducer 创建为Store对象
let store = createStore(todoApp)
// 使用react-redux 提出的Provider来完成
render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

这里就简单说一下,Provider的作用. 通过传入store,相当于就酱render方法和store绑定在一起来. 不需要我们像上面一样,显示的将render和store经由subscribe绑定在一起.

看一下Reducer里面的代码:

import { combineReducers } from 'redux'
// 剩余...

// 将两个reducer combine在一起. 将白了就是按顺序调用. 相当于路由注册的意思
const todoApp = combineReducers({
  todos,
  visibilityFilter
})

这里顺带提一下combineReducers的用法.

combineReducers

该方法,将多个reducer结合在一起,组成一个obj,用来监听dispatch触发的改变.

// 艹,感觉也没什么说的. 后面再深入源码看看吧
const todoApp = combineReducers({
  reducer1,
  reducer2,
  reducer3
})

每个reducer里面的内容,我就不过多赘述了.简单的看一下:

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        todo(undefined, action)
      ]
    ...
    default:
      return state
  }
}

回到上面的index.js,我们来看一下App.js里面做了什么.

// 其实也没什么
import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

export default App

这里, 再次提醒大家一个概念.container. 看一下上面那个图。
说道 container 就不得不提一下 connect 方法。

connect

connect 是 react-redux提出的. 由于 react将state交给了外部管理. 所以,connect 这个就需要起到一个pipe的效果.将components 变装为container.通常connect,需要在Provider包裹的组件中使用.如上面

  <Provider store={store}>
    <App />
  </Provider>

connect能做的事情有两个,一个是传递dispatch,一个是传递store.

// 只注入dispatch,不监听store
export default connect()(TodoApp);

// 正常一点的写法就是

let AddTodo = ({ dispatch }) => {
}
AddTodo = connect()(AddTodo)

还有一个是:

// 注入state.用来监听全局store的变化
// 官方给出的建议是,最小片断化. 即每个container 只捕获属于他一部分的state,这样能保证性能的最优

function mapStateToProps(state) {
  return { todos: state.todos };
}

export default connect(mapStateToProps)(TodoApp);

然后是两个混合传入:

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

// 返回和redux store连接的新组件
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

事实上, connect可以接受4个参数:

// 基本格式为:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
  • [mapStateToProps(state, [ownProps]): stateProps] (Function): 这个方法相当于就是用来传入state的.

    • state: 当前store的内容。

    • ownProps: 为当前组件接受到的Props. 相当于就是把react的props给放到这个参数里面

    • 返回值: 必须是一个对象.他会与props结合.

  • 该函数的用法也没啥说的. 就注意一点, 如果store发生改变,则会触发该函数

第二个是:

  • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): 该函数通常使用来注册,触发函数一块. 该方法是将compnents和container连接的关键方法.因为他的dispatch可以触发store的改动,从而使状态整体更新. dispatch就是触发函数,onwProps就是组件传递的属性.

    • 返回值: 返回通过dispatch绑定的对象集合

还有两个参数,这里就不做过多的解释了. 友情参考:connect 解析

在Todos里面,使用到比较复杂的connect 代码为:

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

// 返回和redux store连接的新组件
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

看一下,TodoList实际接受的情况:

const TodoList = ({ todos, onTodoClick }) => (
    //...
)

这应该很清楚了. 你通过connect 返回的对象,最后会通过Object.assign()进行合并. 所以,你对象的名字需要取一样的。这点需要注意.
上面写了这么多,逻辑可能会有点乱,我们大致来理一理.

react+redux 逻辑关系

开始的时候,我们看了一下简单的redux demo. 发现,里面并没有出现Action的行为,大体只看到的reducer. 所以,在简单demo中,我们的整体架构为:

easy redux framework

这里,我将action给故意隐藏了. 或者说实在一点,你就可以把dispatch当成是action. 这样理解也没问题.

如果涉及到比较复杂的,那么整个流程图,我这里就不用上面四大天王的形式了.我们深入到方法进行分析.

complex redux framework


villainhr
7.8k 声望2.2k 粉丝