Redux学习笔记

Redux并不是只能在React应用中使用,而是可以在一般的应用中使用。
一、首先来看一下redux的工作流:

ca4884ebcb8e8253b0948de9707e865.png

ReactComponents可以看做'借书人',ActionCreators可以看做‘我要借本书’,Store可以看做图书管理员,Reducers可以看做'电脑/手册'

  • store是唯一的,只有store能够改变自己的内容。唯一改变state的方法是触发action
  • 借书人(ReactComponents)通过触发action告诉图书管理员(Store)我要借本书(ActionCreators)。图书管理员(Store)收到消息后通过查阅电脑/手册(Reducers),将书(state)借给借书人(ReactComponents)
  • action实质上是一个对象。我们通过ActionCreators统一创建action(定义很多个方法,每个方法都导出这个action对象)
const action = {
  type: INPUT_CHANGE,
  value: e.target.value
}
// store.dispatch(action)
// actionCreators.js
export const onInputChange = (value) => ({
    type: INPUT_CHANGE,
    value
})
// store.dispatch(onInputChange(e.target.value))
  • store.dispatch(action) 当store接收到action后,会将previousState和action自动转发给reducer, reducer处理完成相应的逻辑后将新修改的newState返回给store。store接收到reducer传来的新的state后会替换掉原来旧的state,从而实现store里的state的更新
  • reducer可以接收state,但绝不能修改state。所以要将从store接收到的state深拷贝一份,返回新更改的state
  • reducer必须是纯函数。纯函数指的是,给定固定的输入,就一定会有固定的输出。同时不会有任何副作用。reducer里不能有new Date()、setTimeOut、 ajax请求等
const defaultStore = {
    inputValue: ''
}
export default (state = defaultStore, action) => {
    if (action.type === INPUT_CHANGE) {
       const newState = JSON.parse(JSON.stringify(state))
       newState.inputValue = action.value
       return newState
    }
  return state
}
  • store.subscribe()这个方法是在组件里订阅store.只要store一改变,subscribe里的方法就会自动执行
 constructor (props) {
    super(props)
    this.state = store.getState()
    store.subscribe(this.handelStoreChange)
  }
 handelStoreChange () {
    this.setState(store.getState())
 }

二、store中的方法
Redux中的store是一个保存整个应用state对象树的对象,其中包含了几个方法,它的原型如下:

type Store = {
  dispatch: Dispatch
  getState: () => State
  subscribe: (listener: () => void) => () => void
  replaceReducer: (reducer: Reducer) => void
}
  • dispatch 用于发送action(动作)使用的的方法
  • getState 取得目前state的方法
  • subscribe 注册一个回调函数,当state有更动时会调用它
  • replaceReducer 高级API,用于动态加载其它的reducer,一般情况不会用到

三、使用redux-thunk中间件实现ajax请求

  • redux-thunks是redux的中间件。我们在创建store的时候,就使用中间件。
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
const Combine =  compose(applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
var store = createStore(reducer, Combine)
export default store
  • 使用了redux-thunk之后,actionCreater可以return出去一个函数(正常情况下return一个对象)
export const initListData = (list) => ({
    type: INIT_LIST,
    list
})
export const getListData = () => {
    return dispatch => {
        axios.get('/api/todolist')
        .then((res) => {
            const data = res.data
            dispatch(initListData(data))
        })
    }
}
  • 只有使用了thunk这个中间件,action才能是个函数,当store发现action是个函数时,会自动执行这个函数
 componentDidMount () {
    // axios.get('/api/todolist')
    // .then((res) => {
    //   console.log(res.data);  
    //   store.dispatch(initListData(res.data))
    // })
    store.dispatch(getListData())  
  }

当我们把异步请求放在组件里的生命周期里时,随着代码量的增加,这个生命周期函数可能会变得越来越复杂,组件变得越来越大。所以建议还是将这种复杂的业务逻辑异步代码拆分到一个地方去管理。
现在借助redux-thunk 将异步请求放在actionCreator中去管理。
放在这里又带来一个好处,就是自动化测试变得很简单,比测试组件的生命周期函数简单的多。

  • redux中间件指的是action和store之间。action通过dispatch方法被传递给store。实际上中间件就是对dispatch方法的一个封装
    d7f70ee27ee955877023b3ec74db848.png

最原始的dispatch方法,接收到一个对象之后会把这个对象传递给store.当有了中间件(对dispatch方法进行升级之后),当传递一个函数时,dispatch不会把这个函数直接传给store,而是先执行这个函数.所以dispatch会根据参数的不同做不同的事情。

四、使用redux-saga中间件实现ajax请求

  • redux-thunk中间件是把异步操作放在action里,redux-suga是单独把异步逻辑拆分出来放到一个文件里去管理.
  • saga里面都是generator函数
  • saga.js中会接收到每一次派发出的action
  • takeEvery函数接收到相应的action类型,会执行第二个参数的方法,将相应的异步逻辑写在第二个参数的方法中。建议写成generator函数。
import {createStore, applyMiddleware, compose} from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducer'
import todoSaga from './saga'
const sagaMiddleware = createSagaMiddleware()
const enhancers =  compose(applyMiddleware(sagaMiddleware),window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
var store = createStore(reducer, enhancers)
sagaMiddleware.run(todoSaga)
export default store
componentDidMount () {
  store.dispatch(getListData())  
}
export const initListData = (list) => ({
    type: INIT_LIST,
    list
})
export const getListData = () => ({
    type: GRT_INIT
})
import {put, takeEvery} from 'redux-saga/effects'
import {GRT_INIT} from './actiontypes'
import {initListData} from './actionCreators'
import axios from 'axios'
function* asyncGetList () {
    try {
        const res = yield axios.get('/api/todolist')
        const action = initListData(res.data)
        yield put(action)
    } catch (err) {
        console.error(err)     
    }
}
function* todoSaga () {
    yield takeEvery(GRT_INIT, asyncGetList)
}
export default todoSaga
  • takeEvery和takeLatest的区别:
    takeEvery每次触发都会执行,不会中断;
    takeLatest连续触发,只执行最后一次触发的结果(比如点击提交按钮,避免提交多次)
  • 调用promise的地方用call语句包裹起来
function* fetchUser() {
  const user = yield call(axios.get, 'https://jsonplaceholder.typicode.com/users');
  console.log(user)
}
  • 并发请求可以使用yeild all([ ])把多个请求包起来

    image.png

  • 当saga函数特别多时如何组织文件?
    方法一:


    image.png

使用fork关键字运行函数,上面例子相当于:


image.png

因为userSagas打印出来是个对象,值是watchFetchUser函数

方法二:在各自文件入口维护好sage并导出来

image.png

然后在根saga文件中使用yeild all [ ]
image.png

五、react-redux的使用

  • provider将store提供给内部所有的组件。渲染根组件时使用即可。

<Provider /> 是由 React Redux 提供的高阶组件,用来让你将 Redux 绑定到 React

import React from 'react'
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './App';

let store = createStore(todoApp)

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

推荐阅读更多精彩内容