Redux初步理解

Redux笔记

参考理解

Redux 中文文档
Redux 阮一峰

严格的单向数据流是Rduex设计核心。

Redux简单概括:单一的state是存储在store中,当要对state进行更新的时候,首先要发起一个action(通过dispatch

函数),action的作用就相当于一个消息通知,用来描述发生了什么(比如:增加一个TODO),然后reducer会根据action来
进行对state更新,这样就可以更新新的state去渲染View.

从不直接修改 state 是 Redux 的核心理念之一, 所以你会发现自己总是在使用 Object.assign() 创建对象拷贝, 而拷贝中会包含新创建或更新过的属性值。在下面的 todoApp 示例中, Object.assign() 将会返回一个新的 state 对象, 而其中的 visibilityFilter 属性被更新了:

function todoApp(state = initialState, action) {
    switch (action.type) {
      case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
      visibilityFilter: action.filter
     })
   default:
    return state
  }
}

Redux适用场景

  1. 用户的使用方式复杂(Ul界面复杂,操作内容多)
  2. 不同身份的用户有不同的使用方式(普通用户和管理员)
  3. 多个用户之间可以协作
  4. 与服务器有大量交互
  5. View要从多个来源获取数据

从组件看,存在以下场景可以考虑使用Redux

  1. 某个组件的状态需要共享
  2. 某个状态需要在任何地方可以拿到
  3. 一个组件需要改变全局状态
  4. 一个组件需要改变另一个组件的状态

要点

  1. 应用中所有的state都以一个对象树的形式存储在一个单一的store中
  1. 唯一改变state的办法就是触发action,一个描述发生什么的对象
  2. 为了描述action如何改变state树,需要编写reducers.

store

store 充当一个容器,用来保存数据的地方。也可以理解成存储state的地方。整个应用 只能 有一个Store
由于整个应用只有一个store,所以store保存了所有的数据。可以通过store.getState()获取当前时刻的state.

store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但是多数情况下你会使用 react-redux 提供的 connect() 帮助器来调用。

action

Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
action是由用户操作view产生的。view的改变产生action,action的改变会传到store,进而影响state的改变。

import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});
通过store.dispatch()将action传递到store里面。

reducer

store 接收到action后,必须给出一个新的state,从而是view做出变化。这样的一个计算过程叫做Reducer.
Reducer是一个 函数,接收action和当前的state作为参数,然后返回一个新的state.

const reducer = function (state, action) {
  // ...
  return new_state;};

因为Reducer函数负责生成state,而整个应用只有一个state,所以当state非常大的时候,导致Reduce函数也非常的大,
根据action不同的种类,我们可以将reducer拆分为多个小的reducer,最后再合成一个大的reducer。

const chatReducer = (state = defaultState, action = {}) => {
const { type, payload } = action;
switch (type) {
case ADD_CHAT:
  return Object.assign({}, state, {
    chatLog: state.chatLog.concat(payload)
  });
case CHANGE_STATUS:
  return Object.assign({}, state, {
    statusMessage: payload
  });
case CHANGE_USERNAME:
  return Object.assign({}, state, {
    userName: payload
  });
default: return state;
}};
上面这个Reducer包含了3个action,显得比较大,比较臃肿。
我们可以对其进行拆分函数:
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}};
这样一个大的Reducer函数就拆分成三个小函数,每个小函数负责生成对应属性。这样就可以把小的函数理解成子reducer函数。
这就与react的根组件与子组件的概念吻合,根组件对应最终生成的大的Reducer,而子组件对应每个子reducer。

拆开成子reducer
function todos(state = [], action) {
  switch (action.type) {
case ADD_TODO:
  return [
    ...state,
    {
      text: action.text,
      completed: false
    }
  ]
case TOGGLE_TODO:
  return state.map((todo, index) => {
    if (index === action.index) {
      return Object.assign({}, todo, {
        completed: !todo.completed
      })
    }
    return todo
  })
default:
  return state
 }
  }
 function visibilityFilter(state = SHOW_ALL, action) {
   switch (action.type) {
case SET_VISIBILITY_FILTER:
  return action.filter
default:
  return state
  }
  }
  function todoApp(state = {}, action) {
    return {
   visibilityFilter: visibilityFilter(state.visibilityFilter, action),
   todos: todos(state.todos, action)
}}

通过使用redux提供的combinReducers()来实现上面todoApp做的事情
import { combineReducers } from 'redux';
  const todoApp = combineReducers({
  visibilityFilter,
  todos
 })
 上面的代码等价于下面的代码
 export default function todoApp(state = {}, action) {
   return {
     visibilityFilter: visibilityFilter(state.visibilityFilter, action),
     todos: todos(state.todos, action)
  }}

export default todoApp;

Redux提供combineReducers方法,用于将定义的各个子Reducer函数合并成一个大的Reducer.

import { combineReducers } from 'redux';
const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

理解

import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener); 

通过 reducer 创建一个 store ,每当我们在 store 上 dispatch 一个 action ,store 内的数据就会相应地发生变化。

Store提供了三种方法:store.getState(),store.dispatch(),store.subscribe().
getState()用来获取store里面当前的state。
dispatch()用来传递action到store里面,可以在任何地方调用 store.dispatch(action),包括组件中、XHR 回调中、甚至定时器中。
subscribe()用来监听事件,接受的参数应当是个函数,该函数应当是在这里获得store的最新state然后用来改变组件state的。

正常的思路应该是view发出action通过dispatch()传递给reducer进行相关的计算从而得出新的state。观察上面创建store的代码,可以发现我们是
先把reducer这个计算函数“放入”store里面,所以我们就可以实现当我们把action通过dispatch()传递给store后,不需要自己手动去调用reducer,
store会自己自动调用。

怎么使用subscribe()订阅来更新ui,connect如何使用

使用方法

明智的做法是只在最顶层组件

使用React-redux

首先在最外层容器中,把所有内容包裹在 Provider 组件中,将之前创建的 store作为 prop 传给 Provider 。

参考理解

const App = () => {
  return (
    <Provider store={store}>
      <Comp/>
    </Provider>
  )
};

Provider 内的任何一个组件(比如这里的 Comp ),如果需要使用 state 中的数据,就必须是「被 connect 过的」组件——使用 connect 方法对「你编写的组件( MyComp )」进行包装后的产物。

class MyComp extends Component {
     // content...
}

const Comp = connect(...args)(MyComp);

connect的使用方法 参考理解

connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])

connect的作用:连接React组件与Redux store会自己自动调用。
connect的第一个参数是mapStateToProps函数, 该函数允许我们将我们将store中的数据作为props绑定到组件上。

mapStateToProps(state, ownProps) : stateProps。
const mapStateToProps = (state) => {
   return {
      count: state.count
    }
}
  1. mapStateToProps函数的第一个参数就是Redux的store(整个state)从上面的示例我们可以看出我们没有将store中所有的数据(state)全部
    传入connect的组件,我们可以根据所要连接组件所需的props,从store中的state动态的输出该组件需要的props.
  2. mapStateToProps函数的第二个参数ownProps,是连接组件的props.

使用ownProps示例:(比如点击人员列表查看人员详细信息,点击事件传递组件(该组件只维护一个用户信息)人员Id属性props,然后可以通过这个组件自己的props去store获取对应数据)

const mapStateToProps = (state, ownProps) => {
  // state 是 {userList: [{id: 0, name: '王二'}]}
    return {
        user: _.find(state.userList, {id: ownProps.userId})
     }
   }
class MyComp extends Component {
 static PropTypes = {
    userId: PropTypes.string.isRequired,
    user: PropTypes.object
  };
render(){
    return <div>用户名:{this.props.user.name}</div>
    }
}

const Comp = connect(mapStateToProps)(MyComp);

当Redux中的store(state)变化或者ownProps变化的时候,mapStateToProps都会被调用,计算出一个新的stateProps,(再与ownProps merge后)更新给组件.如果省略了这个参数,你的组件将不会监听 Redux store.

mapDispatchToProps(dispatch, ownProps): dispatchProps

connect 的第二个参数是 mapDispatchToProps,它的功能是,将 action 作为 props 绑定到组件上.如果省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。该函数如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,而且这个对象会与 Redux store 绑定在一起,其中所定义的方法名将作为属性名,合并到组件的 props 中。如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起

不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给组件。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法

代码示例

import React, {Component} from 'react'
class Counter extends Component {
    render() {
        //从组件的props属性中导入四个方法和一个变量
        const {increment, decrement, counter} = this.props;
        //渲染组件,包括一个数字,四个按钮
        return (
            <p>
                Clicked: {counter} times
                {' '}
                <button onClick={increment}>+</button>
                {' '}
                <button onClick={decrement}>-</button>
                {' '}
            </p>
        )
    }
}
export default Counter;

import { connect } from 'react-redux'
import Counter from '../components/Counter'
import actions from '../actions/counter';
//将state.counter绑定到props的counter. 哪些 Redux 全局的 state 是我们组件想要通过 props 获取的?
const mapStateToProps = (state) => {
    return {
        counter: state.counter
    }
};
//将action的所有方法绑定到props上.哪些 action 创建函数是我们想要通过 props 获取的?
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        increment: (...args) => dispatch(actions.increment(...args)),
        decrement: (...args) => dispatch(actions.decrement(...args))
    }
};
//通过react-redux提供的connect方法将我们需要的state中的数据和actions中的方法绑定到props上
export default connect(mapStateToProps, mapDispatchToProps)(Counter)

Middleware

作用的位置:位于action被发起之后,到达reducer之前的扩展点

Redux moddleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer

理解:在action和reducer中间插入middleware,通过改变数据流,实现异步action.

可以干什么:进行日志记录、创建崩溃报告、调用异步接口或者路由

怎么使用Middleware:redux 提供了applyMiddleware这个api来加载middleware

项目中使用Middleware的方式:

import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import rootReducer from './reducers'

const loggerMiddleware = createLogger()

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  loggerMiddleware
)(createStore)

export default function configureStore(initialState) {
  return createStoreWithMiddleware(rootReducer, initialState)
}

applyMiddleware源码:

export default function applyMiddleware(...middlewares) {            return (next)  => 
        (reducer, initialState) => {

              var store = next(reducer, initialState);
              var dispatch = store.dispatch;
              var chain = [];

              var middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
              };

              chain = middlewares.map(middleware =>
                            middleware(middlewareAPI));
              dispatch = compose(...chain, store.dispatch);
              return {
                ...store,
                dispatch
              };
           };
}

applyMiddleware源码使用了柯里化(理解:将多参数函数转化为单参数函数执行,然后返回结果接受剩下的参数继续执行)。
柯里化的过程存在闭包,因而store是最新并且相同的。

//  柯里化简单示例
function add(x){
   return function(y){
      return x+y;
   }
}
console.log(add(5)(10)); // 15

对于中间件的理解:同步action:action发出后,reducer立即算出state。异步action:action发出以后,过一段时间再执行reducer。怎样是reducer在异步操作结束后自动执行?使用的解决方法就是:中间件。

同步action只要发出一种action,异步action一般要发出三种action。

异步action一般要发出的三种action:

  1. 操作发起时的action
  2. 操作成功时的action
  3. 操作失败时的action

同步action的执行流程:

action-->dispatch-->reducers

当我们执行异步action的时候可能还需要在action传递到reducer这个过程中处理一些其他的事,执行一些额外的函数。
执行流程:

action-->dispatch-->额外的函数代码-->reducer

引入中间件,将额外需要执行的函数放到中间件中执行

action-->dispatch-->middleware1-->...-->middleware3-->reducer

我们都知道action creator是一个纯js函数,返回的是一个对象。将action creator通过dispacth传递到reducer从而改变state.
而通过使用中间件,action creator返回的将是一个函数(或者其他的类似Promise对象等),返回的函数的参数是dispatch和getState这个两个redux方法。执行异步请求的时候,第一个action表示操作开始,该操作类似同步action操作。第二个action的发出是在返回的函数中(因为返回的函数的参数含有getState,所有state是最新的。而参数中又含有dispatch,所有可以通过该方法执行第二个action)。

参考资料一;参考资料二;
参考资料三

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容