23. redux

redux数据请求机制


image.png

图片来源于:https://segmentfault.com/img/bVRQRK?w=1205&h=618

action & actionCreator
action creator 就是函数而已,负责构建一个 action (是的,action creator 这个名字已经很明显了)并返回它。通过几行简单的代码就可以解释清楚了!

const actionCreator = function () {
  return {
    type : 'AN_ACTION'
  }
}

一般约定 action 是一个拥有 type 属性的对象。

console.log(actionCreator())
//  { type: 'AN_ACTION' }

reducer
Reducer 函数只是一个纯函数,它接收应用程序的当前状态以及发生的 action,然后返回修改后的新状态(或者有人称之为归并后的状态)。Reducer 函数是 action 的订阅者。

const reducer = function (state = {}, action) {
  console.log('reducer was called with state', state, 'and action', action);

  return state;
}

Store
以上,action描述“发生了什么”,而reducer根据action来更新state。但是他们两者之间是如何关联的呢?

不用担心,Redux 会帮你把action和reducer连接起来。

我们把 Redux实例称为 store 并用以下方式创建:

import { createStore } from 'redux'

const store_0 = createStore(() => {})

注意:在createStore时,需要给它传入一个 reducer 函数。

每当一个action发生时,Redux都能调用这个函数。往 createStore 传 Reducer 的过程就是给 Redux绑定 action处理函数(也就是Reducer)的过程。

接下来,试着在 Reducer 中打印一些 log

const reducer = function (...args) {
  console.log('Reducer was called with args', args)
}

const store_1 = createStore(reducer)
// 输出:Reducer was called with args [ undefined, { type: '@@redux/INIT' } ]

我们没有dispatch(分发)任何action,但是reducer被调用了!这是由于初始化应用state的时候,Redux dispatch 了一个初始化的 action ({ type: '@@redux/INIT' })。reducer的入参为(state, action)。state还没有被初始化,自然为undefined。

如何读取store中的state?

Redux为我们提供了store.getState()方法。

import { createStore } from 'redux'

const reducer_2 = function (state = {}, action) {
  console.log('reducer_2 was called with state', state, 'and action', action)

  return state;
}

const store_2 = createStore(reducer_2)
// 输出: reducer_2 was called with state {} and action { type: '@@redux/INIT' }

console.log('store_2 state after initialization:', store_2.getState())
// 输出: store_2 state after initialization: {}

如何dispatch action?

我们需要使用store.dispatch(action)方法。

// 接以上代码
const anAction = {
  type : 'AN_ACTION'
}
store_2.dispatch(anAction);
// 输出:reducer_2 was called with state {} and action { type: 'AN_ACTION' }
combineReducers
combineReducer用于合并Reducers,并且合并对应的State。

const userReducer  = function (state = {}, action) {
  console.log('userReducer was called with state', state, 'and action', action)

  switch (action.type) {
    // etc.
    default:
      return state;
  }
}
const itemsReducer = function (state = [], action) {
  console.log('itemsReducer was called with state', state, 'and action', action)

  switch (action.type) {
    // etc.
    default:
      return state;
  }
}
import { createStore, combineReducers } from 'redux'

const reducer = combineReducers({
  user  : userReducer,
  items : itemsReducer
})

// 输出:
// userReducer was called with state {} and action { type: '@@redux/INIT' }
// userReducer was called with state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_9.r.k.r.i.c.n.m.i' }
// itemsReducer was called with state [] and action { type: '@@redux/INIT' }
// itemsReducer was called with state [] and action { type: '@@redux/PROBE_UNKNOWN_ACTION_4.f.i.z.l.3.7.s.y.v.i' }

var store_0 = createStore(reducer)

// 输出:
// userReducer was called with state {} and action { type: '@@redux/INIT' }
// itemsReducer was called with state [] and action { type: '@@redux/INIT' }

console.log('store_0 state after initialization:', store_0.getState())
// 输出:
// store_0 state after initialization: { user: {}, items: [] }

回过头来看看文章开头的数据流向图
View组件通过click等事件,dispatch一个(actionCreator返回的)action,通过Store把当前状态state和action传递给订阅者reducer函数,reducer返回一个新的状态存储在Store中,Store又把新的State传递给View组件触发组件更新。

为了将Redux和React联系到一起。就需要用到React-Redux这个库。

import { connect } from 'react-redux'
const containerComponent = connect(mapStateToProps, mapDispatchToProps)(presentationalComponent)
简单来说,mapStateToProps和mapDispatchToProps就是分别把Redux的state,和dispatch(action)映射到React组件中作为props。connect将展示组件(presentationalComponent)封装成高阶的容器组件(containerComponent)。state的更新意味着props更新。

上诉摘自于https://segmentfault.com/a/1190000010407887

请求机制:

image.png

接下来上项目吧:

  1. 安装依赖:
    cnpm install redux react-redux redux-thunk axios --save
  2. 目录结构如下


    image.png

3.代码如下:
(1)src/index.js

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

import store from './store';

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

(2) src/App.js

import React, { Component } from 'react';

import { connect } from 'react-redux';

import { requestBlog } from './actions/blog';

class App extends Component {
  //第一步:通过UI动作进入后,在componentDidMount请求数据
  //通过requestBlog()找到Blog.js的actionCreater
  componentDidMount() {
    this.props.requestBlog();
  }
  render() {
    //随着blog.js中的值被更改,this.props的值(依赖于state.blog的blogList)也被更改
    const {
      blogList,
      isLoading
    } = this.props;
    return (
      <div className="App">
        {
          isLoading
          ?
          <div className="loading">加载中……</div>
          :
          <ul>
            {
              blogList.map(blog => {
                const {
                  id,
                  title,
                  body
                } = blog;
                
                return (
                  <li key={id}>
                    <h2>{title}</h2>
                    <p>{body}</p>
                  </li>
                )
              })
            }
          </ul>
        }
      </div>
    );
  }
}

const mapState = state => {
  return {
    blogList: state.blog.list,
    isLoading: state.ux.isLoading
  }
}

export default connect(mapState, { requestBlog })(App);

(3)src/store.js

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

import rootReducer from './reducers';

export default createStore(
  rootReducer,
  applyMiddleware(thunk)
);

(4) src/actions/actionType.js

export const SHOW_LOADING = 'SHOW_LOADING';
export const HIDE_LOADING = 'HIDE_LOADING';
export const REQUEST_BLOG_SUCCESS = 'REQUEST_BLOG_SUCCESS';

(5) src/actions/ux.js

import {
  HIDE_LOADING,
  SHOW_LOADING
} from './actionType';

export const showLoading = () => {
  return {
    type: SHOW_LOADING
  }
}

export const hideLoading = () => {
  return {
    type: HIDE_LOADING
  }
}

(6) src/actions/blog.js

import axios from 'axios';

import {
  REQUEST_BLOG_SUCCESS
} from './actionType';

import {
  showLoading,
  hideLoading
} from './ux';

//App.js中的componentDidMount中调用的requestBlog
export const requestBlog = () => {
  return (dispatch) => {
    // 开始请求之前先显示loading
    dispatch(showLoading());
    axios.get('http://jsonplaceholder.typicode.com/posts')
      .then(resp => {
        // 请求返回了数据之后,dispatch一个动作, 并且把返回的值传到这个动作所对应的reducer去处理
        dispatch({
          type: REQUEST_BLOG_SUCCESS,
          // 修改数据
          payload: {
            list: resp.data
          }
        })
      })
      .catch(err => {
        // 实际上,这里也应该去做一个dispatch,用于reducer的错误处理
        console.log(err);
      })
      .finally(()=> {
        // 请求结束之后,隐藏loading
        dispatch(hideLoading())
      })
  }
}

(7) src/reducers/ux.js

import {
  HIDE_LOADING,
  SHOW_LOADING
} from '../actions/actionType';

const initialState = {
  isLoading: false
}

export default (state=initialState, action) => {
  switch(action.type) {
    case SHOW_LOADING:
      return {
        ...state,
        isLoading: true
      };
    case HIDE_LOADING:
      return {
        ...state,
        isLoading: false
      };
    default:
      return state;
  }
}

(8)src/reducers/blog.js

import { REQUEST_BLOG_SUCCESS } from '../actions/actionType';
const initialState = {
  list: [{
    id: 1,
    title: 'niubility',
    body: 'h5 1806'
  }]
}
export default (state=initialState, action) => {
  switch(action.type) {
    case REQUEST_BLOG_SUCCESS:
      return {
        ...state,
        list: action.payload.list
      };
    default:
      return state;
  }
}

(9)src/reducers/index.js

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

推荐阅读更多精彩内容

  • ​​我不知道,为什么家里那三个大的叫老母亲为“娘”,叫得好亲好糯,让人心口热热的。我从来都是叫一声“妈”。隔了一层...
    铭玥咏全阅读 623评论 0 3