美文网首页
Play With Redux

Play With Redux

作者: 梁某人的剑 | 来源:发表于2020-04-11 16:19 被阅读0次

Redux为什么而生

JQ时代的野蛮生长给前端生态带来进步(模块化,交互设计,响应式),同时也带来了跟多问题,这里我们foucs在以下两个问题:
1、前端代码难以复用(包括不同的语言)
2、页面的交互逻辑过于复杂

由此逐渐催生出现在的主流前端组件化框架,也是所谓的MVVM,为了解决这两个问题,大家给出了virtual DOM的概念跟CSS in JS的概念,将html,css,js代码统一到具有计算能力的js中,将曾经分离的页面结构/样式/交互逻辑三者统一并抽象出组件的概念,达到整体复用的目的。但是UI的动态的,数据与视图的双向绑定技术使得组件能够独立维护自己的状态数据,使组件成为了真正意义上的独立UI单元。可是看似完美的解决方案依旧带来了新的问题:



双向绑定的方式来将Model的数据展现到View。当Model中的数据发生变化时,一个或多个View会发生变化;当View接受了用户输入时,Model中的数据则会发生变化。在实际的应用中,当一个Model中的数据发生变化时,也有可能另一个相关的Model中的数据会被同步更新。这样,很容易出现的一个现象就是连锁更新(Cascading Update),Model可以更新Model,Model可以更新View,View也可以更新Model。你很难去推断一个界面的变化究竟是由哪个局部的功能代码引起。

设计理念

UI视图中混乱的数据交叉引用像机房中的复杂线路一样让人头大,多属性对想数据的强耦合破坏了组件的独立性。因此我们不得不发明新的解决方案。facebook给出Flux:将数据流向编程单向,引入Store、Action、Action Creators和Dispatcher等概念来管理数据流

  • Store,它是数据操作的唯一地方,当数据发生变化时,它可以使用emit方法向其它地方发送名为'change'的广播,告知它们store已经发生了变化。
  • Action
    它是用来描述一个行为的对象,每个action里都包含了某个行为的相关信息。
// Action
{
  actionName: 'ADD_COMMENT',
  data: {
    'id': '1',
    'comment': 'Haha'
  }
}
  • Flux流程图


    Flux流程图
  • Dispatcher
    它是一个信息分发中心,它是action和store的连接中心。它可以使用dispatch方法执行一个action,并且可以用register方法注册回调,在回调中处理store中的数据。

  • View
    视图层监听了'change'事件,一旦change事件被触发,视图层就会调用setState方法来更新相应的UI State。

Redux是Flux的一种实现

Redux于2015年由Dan Abramov和Andrew Clark共同撰写,旨在简化和简化Flux引入的许多概念。

  • Redux和Flux类似
    • 它们都强调单向数据流的重要性
    • 都通过type字段的Action来修改数据
  • Redux的不同之处
    • Redux只有一个store(使用单一数据树)
    • Redux去掉了dispatcher的概念
    • store只负责储存数据,不需要根据action来修改判断如何修改数据,这个职责交给了reducer

唯一状态树store

Store — 数据存储中心,同时连接着Actions和Views(React Components)。

  1. Store需要负责接收Views传来的Action
    • 每一个Store实例都拥有dispatch方法,Views只需要通过调用该方法,并传入action对象作为形参,Store自然就就可以收到Action
    • store.dispatch({type: 'INCREASE'});
  2. 然后,根据Action.type和Action.payload对Store里的数据进行修改
    • 数据修改逻辑写在Reducer(一个纯函数)里,Store实例在创建的时候,就会被传递这样一个reducer作为形参,这样Store就可以通过Reducer的返回值更新内部数据了
  3. 最后,Store还需要通知Views,数据有改变,Views便去获取最新的Store数据,通过setState进行重新渲染组件(re-render)。
    • 每一个Store实例都提供一个subscribe方法,Views只需要调用该方法注册一个回调(内含setState操作),之后在每次dispatch(action)时,该回调都会被触发,从而实现重新渲染;对于最新的Store数据,可以通过Store实例提供的另一个方法getState来获取

函数式与不可变性——Reducer

  • Reducers是一个纯函数,接受state和action两个参数
  • 不会在原state数据上做修改,Redux认为状态是不可变的
  • 如果需要修改state数据,则编辑state数据副本,修改之后替换原数据
const counter = (state = { counter: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT_COUNTER':
      return {
        ...state,
        counter: state.counter + 1
      }
    default:
      return state
  }
}

弱化唯一数据源带来的数据结构问题——combineReducers

将针对不同类型的数据比如:comment跟post定制,他们具有不同的数据更新规则(Action),所以可以定制不同的reducer。然后使用redux提供的combineReducers接口自下而上拼接起来形成rootReducer,那么rootReducer的返回值就是整个object tree。

import { combineReducers } from 'redux';

// 叶子reducer
function aReducer(state = 1, action) {/*...*/}
function cReducer(state = true, action) {/*...*/}
function aReducer(state = [2, 3], action) {/*...*/}

const eReducer = combineReducers({
  e: eReducer
});

const bReducer = combineReducers({
  c: cReducer,
  d: dReducer
});

// 根reducer
const rootReducer = combineReducers({
  a: aReducer,
  b: bReducer
});

中间件——Middlerwares

中间件讲究的是对数据的流式处理,比较优秀的特性是:链式组合,由于每一个中间件都可以是独立的,因此可以形成一个小的生态圈。

在Redux中,Middlerwares要处理的对象则是:Action。

每个中间件可以针对Action的特征,可以采取不同的操作,既可以选择传递给下一个中间件,如:next(action),也可以选择跳过某些中间件,如:dispatch(action),或者更直接了当的结束传递,如:return。

标准的action应该是一个plain object,但是对于中间件而言,action还可以是函数,也可以是promise对象,或者一个带有特殊含义字段的对象,但不管怎样,因为中间件会对特定类型action做一定的转换,所以最后传给reducer的action一定是标准的plain object。

  • [redux-thunk]里的action可以是一个函数,用来发起异步请求。
  • [redux-promise]里的action可以是一个promise对象,用来更优雅的进行异步操作。
  • [redux-logger]里的action就是一个标准的plain object,用来记录action和nextState的。

中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。


// 中间件的用法:扩展store
const enhancer = applyMiddleware(
  thunk,
  logger
);

const store = createStore(rootReducer, initialState, enhancer);

配合使用react-redux

  • Provider组件

Provider存在的意义在于:想通过context的方式将唯一的数据源store传递给任意想访问的子孙组件。

  • connect方法

让Component与Store进行关联,即Store的数据变化可以及时通知Views重新渲染。

Dojo

Counter

使用组件的state实现

class Counter extends Component {
  //初始化state的值
  constructor(props) {
    super(props);
    this.state = {counter: 0};
  }

  //这里采用了调用setState方法的两种方式,注意差别
  handleIncrement() {
    this.setState({
      counter: this.state.counter + 1
    });
  }
  //这里的两个Handle方法,一个是箭头函数,一个是普通函数(使用的时候要绑定上下文this)
  handleDecrement = () => {
    this.setState(preState => ({
      counter: preState.counter -1
    }))
  };

  render() {
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleIncrement.bind(this)}>+</button>
        <button onClick={this.handleDecrement}>-
        </button>
      </div>
    )
  }
}

使用redux实现

  • 定义reducer,初始化数据 + 定义数据变化规则
const initialState = 0;

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state
  }
};
export default counterReducer
  • 拿着reducer创造出数据中心sotre
import {applyMiddleware, createStore} from 'redux'
import logger from 'redux-logger'
import counterReducer from '../reducers/counterReducer'

const store = createStore(counterReducer, applyMiddleware(logger));

export default store
  • 使用redux提供的Provider将store跟整个App绑定,这样子组件就可以通过store.getState()取到store中的值
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './containers/App/App'
import {Provider} from 'react-redux'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>
  , document.getElementById('root'));

  • 将store与Counter组件使用connect接口链接;store中的state值映射为组件的props达到数据动态绑定的效果:dispatch触发数据变化之后组件重绘
import React, {Component} from 'react'
import store from '../../../store/index'
import {connect} from 'react-redux'

class Counter extends Component {
  render() {
    return (
      <div>
        <h1>{this.props.counter}</h1>
        <button onClick={() => {
          store.dispatch({type: 'INCREMENT'})
        }}>+
        </button>
        <button onClick={() => store.dispatch({type: 'DECREMENT'})}>-</button>
      </div>
    )
  }
}

const mapStateToProps = state => ({
  counter: state.counter
});
export default connect(mapStateToProps)(Counter)

Post—Comment

Todo List

https://codesandbox.io/s/github/reactjs/redux/tree/master/examples/todos

Other Examples

https://redux.js.org/introduction/examples

相关文章

网友评论

      本文标题:Play With Redux

      本文链接:https://www.haomeiwen.com/subject/fgpzfqtx.html