redux 之同步数据管理

目录

一、一句话说 redux

二、redux 的成员

1、action

2、reducer

(1)、组件的 reducer

(2)、根 reducer

3、Store

(1)、创建 store

(2)、分发 action

三、redux 的运作流程

1、调用 store.dispatch(action)

2、Action 会触发给 Store 指定的 root reducer

3、Store 会保存 root reducer 返回的状态树

4、简例

四、在 react 中注册使用 redux

1、将 Redux 绑定到 React 实例上

(1)、Provider 组件

(2)、Connect() 方法


一、一句话说 redux

redux 就是:先将需要修改的 state 都存入到 store 里,然后发起一个 action 用来描述发生了什么,然后用 reducers 描述 action 如何改变 state 状态树 ,创建store的时候需要传入reducer,真正能改变 store 中数据的是 store.dispatch API。

redux

 

二、redux 的成员

1、action

Action 是一个单纯的包含 { type, payload } 的对象,其中:

  • type 是一个常量用来标示动作类型;
  • payload 是这个动作携带的数据。

通常使用函数来生成 action,它最后会返回一个 action 对象。比如:

export function addTodo(text) {
  return {
    type: ActionTypes.ADD_TODO,
    text: text
  };
}

创建 action 的函数是一个纯函数pure function)——形如 “(state, action) => state一个函数的返回结果只依赖其参数,并且执行过程中没有副作用。

Action 需要通过 store.dispatch() 方法来发送。所以现在要触发一个动作只要调用 dispatchdispatch(addTodo(text))。稍后会讲到如何拿到 store.dispatch。

2、reducer

(1)、组件的 reducer

Reducer 用来处理 Action 触发的对状态树的更改。

所以一个 reducer 函数会接受 oldState 和 action 两个参数,返回一个新的 state:(oldState, action) => newState

const initialState = {
  a: 'a',
  b: 'b'
};

function someApp(state = initialState, action) {
  switch (action.type) {
    case 'CHANGE_A':
      return { ...state, a: 'Modified a' };
    case 'CHANGE_B':
      return { ...state, b: action.payload };
    default:
      return state
  }
}

【注意】

  • 使用 ES6 的扩展语法来确保不会更改到 oldState 而是返回一个 newState。
  • 对于不需要处理的 action,直接返回 oldState

对上述代码用“策略模式”进行优化:

const initialState = {
  a: 'a',
  b: 'b'
};

function someApp(state = initialState, action) {
  var map = {
    CHANGE_A: function () {
      return { ...state, a: 'Modified a' };
    },
    CHANGE_B: function () {
      return { ...state, b: action.payload };
    },
  }
  if (map[action.type]) {
    return map[action.type]();
  } else {
    return state;
  }
}

redux 的 Reducer 是一个纯函数,所以绝对不要在 reducer 里面做一些引入 副作用 的事情,比如:

  • 直接修改 state 参数对象
  • 请求 API
  • 调用不纯的函数,比如: Data.now() Math.random()

(2)、根 reducer

Redux 里,一个 Store 对应一个 State 状态,所以整个 State 对象就应该由一个总的 reducer 函数管理。但是,如果所有的状态更改逻辑都放在这一个 reducer 里面,显然会变得巨大而难以维护。得益于纯函数的实现,我们只需要让状态树上的每个字段都有一个自己的 reducer 函数来管理,就可以拆分成很小的 reducer 了:

function someApp(state = {}, action) {
  return {
    a: reducerA(state.a, action),
    b: reducerB(state.b, action)
  };
}

对于 reducerA 和 reducerB 来说,他们依然是形如:(oldState, action) => newState 的函数。只是这时候的 state 不是整个状态树,而是树上的特定字段,每个 reducer 只需要判断 action,管理自己关心的状态字段数据就好了。

Redux 提供了一个工具函数 combineReducers() 来简化这种 reducer 合并:

import { combineReducers } from 'redux';

const someApp = combineReducers({
  a: reducerA,
  b: reducerB
});

像 someApp 这种管理整个 State 的 reducer,可以称为根 reducer 即 root reducer。 

3、Store

(1)、创建 store

Store 的作用就是连接 Action 和 Reducer,Store 的具体作用如下:

  • 保持住整个应用的 State 状态树
  • 提供一个 getState() 方法获取 State
  • 提供一个 dispatch() 方法发送 action 更改 State
  • 提供一个 subscribe() 方法注册回调函数监听 State 的更改

创建一个 Store 很容易,将 “root reducer” 函数传递给 createStore() 方法即可:

import { createStore } from 'redux';
import someApp from './reducers';
let store = createStore(someApp);

// 你也可以额外指定一个初始 State(initialState),这对于服务端渲染很有用
// let store = createStore(someApp, window.STATE_FROM_SERVER);

现在我们就拿到了 store.dispatch,可以用来分发 action 了。

(2)、分发 action

// 使用 subscribe 方法创建一个函数,监听 State 的更改
let unsubscribe = store.subscribe(() => console.log(store.getState()));

// 分发 action
store.dispatch({ type: 'CHANGE_A' });
store.dispatch({ type: 'CHANGE_B', payload: 'Modified b' });

// 停止侦听状态更新
unsubscribe();

 

三、redux 的运作流程

redux 的运作流程如下:store.dispatch(action) -> reducer(state, action) -> store.getState(),这整个过程是一个“单向数据流”。

1、调用 store.dispatch(action)

Action 是一个包含 { type, payload } 的对象,它描述了“发生了什么”,比如:

{ type: 'LIKE_ARTICLE', articleID: 42 }
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }
{ type: 'ADD_TODO', text: 'Read the Redux docs.' }

你可以在任何地方调用 store.dispatch(action),比如组件内部,Ajax 回调函数里面等等。

2、Action 会触发给 Store 指定的 root reducer

root reducer 会返回一个完整的状态树,State 对象上的各个字段值可以由各自的 reducer 函数处理并返回新的值。

  • reducer 函数接受 (state, action) 两个参数
  • reducer 函数判断 action.type 然后处理对应的 action.payload 数据来更新并返回一个新的 state

3、Store 会保存 root reducer 返回的状态树

新的 State 会替代旧的 State,然后所有 store.subscribe(listener) 注册的回调函数会被调用,在回调函数里面可以通过 store.getState() 拿到新的 State。

这就是 Redux 的运作流程,接下来看如何在 React 里面使用 Redux。

4、简例

import {createStore} from 'redux';

function count(state = 0, action) {
    switch (action.type) {
        case 'ADD':
            return state + 1
        case 'REDUCER':
            return state - 1;
        default:
            return state
    }
}

// 创建一个仓库
let store = createStore(count);

let currentValue = store.getState();
console.log('当前的值:', currentValue);

// 定义一个监听的方法
let listener = () => {
    const previosValue = currentValue;
    currentValue = store.getState();
    console.log('上一个值:', previosValue, '当前值:', currentValue)
}

// 创建一个监听
store.subscribe(listener);

// 分发任务
store.dispatch({type:"ADD"});
store.dispatch({type:"ADD"});
store.dispatch({type:"ADD"});
store.dispatch({type:"REDUCER"});

 

四、在 react 中注册使用 redux

1、将 Redux 绑定到 React 实例上

react-redux 提供了两个 API:Provider 和 Connect。

(1)、Provider 组件

<Provider> 作为一个容器组件,用来接受 Store,并且让 Store 对子组件可用,用法如下:

import { render } from 'react-dom';
import { Provider } from 'react-redux';
import App from './app';

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

这时候 <Provider> 里面的子组件 <App /> 才可以使用 connect 方法关联 store。 

<Provider> 的源码及其实现原理:

import React, { useMemo, useEffect } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
import Subscription from '../utils/Subscription'

function Provider({ store, context, children }) {
  const contextValue = useMemo(() => {
    const subscription = new Subscription(store)
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription,
    }
  }, [store])

  const previousState = useMemo(() => store.getState(), [store])

  useEffect(() => {
    const { subscription } = contextValue
    subscription.trySubscribe()

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
    return () => {
      subscription.tryUnsubscribe()
      subscription.onStateChange = null
    }
  }, [contextValue, previousState])

  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

if (process.env.NODE_ENV !== 'production') {
  Provider.propTypes = {
    store: PropTypes.shape({
      subscribe: PropTypes.func.isRequired,
      dispatch: PropTypes.func.isRequired,
      getState: PropTypes.func.isRequired,
    }),
    context: PropTypes.object,
    children: PropTypes.any,
  }
}

export default Provider

Provider 利用了 React 一个(暂时)隐藏的特性 Contexts,Context 用来传递一些父容器的属性对所有子孙组件可见,在某些场景下面避免了用 props 传递多层组件的繁琐。

(2)、Connect() 方法

Connect() 方法用来连接 React 组件与 Redux store,不会改变原来的组件类。

Connect() 方法可以接收 4 个可选的参数:connect([mapStateToProps],  [mapDispatchToProps],  [mergeProps],  [options]):

  • [mapStateToProps(state, [ownProps]): stateProps]:该参数是一个函数,用来管理是否允许组件监听 Redux store 的变化。
    • 第一个可选参数是一个函数,只有指定了这个参数,这个关联(connected)组件才会监听 Redux Store 的更新,每次更新都会调用 mapStateToProps 这个函数,返回一个字面量对象将会合并到组件的 props 属性。
    • 如果你省略了这个 mapStateToProps 参数,你的组件将不会监听 Redux store。
    • ownProps 是可选的第二个参数,它是传递给组件的 props,当组件获取到新的 props 时,ownProps 都会拿到这个值并且执行 mapStateToProps 这个函数。
  • [mapDispatchProps(dispatch, [ownProps]): dispatchProps]:该参数可以是一个对象,也可以是一个函数,用来指定如何传递 dispatch 给组件。
    • 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中dispatch方法会将action creator的返回值作为参数执行。这些属性会被合并到组件的 props 中。
    • 如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起(提示:你也许会用到 Redux 的辅助函数 bindActionCreators())。
    • 如果你省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。
    • ownProps 是可选的第二个参数,它是传递给组件的 props,只要组件接收到新 props,mapDispatchToProps 就会被调用。
  • [mergeProps(stateProps, dispatchProps, ownProps): props]:该参数是一个函数,用来处理 mapStateToProps 和 mapDispatchProps 返回的结果。
    • 如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。
    • 如果你省略这个参数,默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。
  • [options]:该参数是一个对象,用来定制 connector 的行为。
    • [pure = true] (Boolean): 如果为 true,connector 将执行 shouldComponentUpdate 并且浅对比 mergeProps 的结果,避免不必要的更新,前提是当前组件是一个“纯”组件,它不依赖于任何的输入或 state 而只依赖于 props 和 Redux store 的 state。默认值为 true。
    • [withRef = false] (Boolean): 如果为 true,connector 会保存一个对被包装组件实例的引用,该引用通过 getWrappedInstance() 方法获得。默认值为 false。

更多 connect() 方法的参数的使用详见官网:https://www.redux.org.cn/docs/react-redux/api.html

Connect() 方法,会返回另一个函数,这个返回的函数用来接受一个组件类作为参数,最后它会返回一个和 Redux store 关联起来的新组件

class App extends Component { ... }

export default connect()(App);

这样就可以在 App 这个组件里面通过 props 拿到 Store 的 dispatch 方法。

 

本文参考:http://caibaojian.com/react/redux-basic.html

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值