1-Redux

Redux

[TOC]

知识点

  • 状态管理器
  • state 对象
  • reducer 纯函数
  • store 对象
  • action 对象
  • combineReducers 方法
  • react-redux
  • provider 组件
  • connect 方法
  • Redux DevTools extension 工具
  • 中间件 - middleware
  • redux-chunk

状态(数据)管理

前端应用越来越复杂,要管理和维护的数据也越来越多,为了有效的管理和维护应用中的各种数据,我们必须有一种强大而有效的数据管理机制,也称为状态管理机制,<u>Redux</u> 就是解决该问题的

Redux

<u>Redux</u> 是一个独立的 <u>JavaScript</u> 状态管理库,与非 <u>React</u> 内容之一

https://www.bootcdn.cn/redux/

核心概念

理解 <u>Redux</u> 核心几个概念与它们之间的关系

  • state
  • reducer
  • store
  • action

<u>state</u> 对象

通常我们会把应用中的数据存储到一个对象树(<u>Object Tree</u>) 中进行统一管理,我们把这个对象树称为:<u>state</u>

<u>state</u> 是只读的

这里需要注意的是,为了保证数据状态的可维护和测试,不推荐直接修改 <u>state</u> 中的原数据

通过纯函数修改 <u>state</u>

什么是纯函数?

纯函数
  1. 相同的输入永远返回相同的输出
  2. 不修改函数的输入值
  3. 不依赖外部环境状态
  4. 无任何副作用

使用纯函数的好处

  1. 便于测试
  2. 有利重构

Reducer 函数

function todo(state, action) {
  switch(action.type) {
    case 'ADD':
      return [...state, action.payload];
      break;
    case 'REMOVE':
      return state.filter(v=>v!==action.payload);
      break;
    default:
      return state;
    }
}

上面的 <u>todo</u> 函数就是 <u>Reducer</u> 函数

  1. 第一个参数是原 <u>state</u> 对象
  2. <u>Reducer</u> 函数不能修改原 <u>state</u>,而应该返回一个新的 <u>state</u>
  3. 第二参数是一个 <u>action</u> 对象,包含要执行的操作和数据
  4. 如果没有操作匹配,则返回原 <u>state</u> 对象

action 对象

我们对 <u>state</u> 的修改是通过 <u>reducer</u> 纯函数来进行的,同时通过传入的 <u>action</u> 来执行具体的操作,<u>action</u> 是一个对象

  • type 属性 : 表示要进行操作的动作类型,增删改查……
  • payload属性 : 操作 <u>state</u> 的同时传入的数据

但是这里需要注意的是,我们不直接去调用 <u>Reducer</u> 函数,而是通过 <u>Store</u> 对象提供的 <u>dispatch</u> 方法来调用

Store 对象

为了对 <u>state</u>,<u>Reducer</u>,<u>action</u> 进行统一管理和维护,我们需要创建一个 <u>Store</u> 对象

Redux.createStore 方法
let store = Redux.createStore((state, action) => {
  // ...
}, []);

todo

用户操作数据的 <u>reducer</u> 函数

[]

初始化的 <u>state</u>

我们也可以使用 <u>es6</u> 的函数参数默认值来对 <u>state</u> 进行初始化

let store = Redux.createStore( (state = [], action) => {
  // ...
} )
getState() 方法

通过 <u>getState</u> 方法,可以获取 <u>Store</u> 中的 <u>state</u>

store.getState();
dispatch() 方法

通过 <u>dispatch</u> 方法,可以提交更改

store.dispatch({
  type: 'ADD',
  payload: 'MT'
})
<u>action</u> 创建函数

<u>action</u> 是一个对象,用来在 <u>dispatch</u> 的时候传递动作和数据,我们在实际业务中可能会中许多不同的地方进行同样的操作,这个时候,我们可以创建一个函数用来生成(返回)<u>action</u>

function add(payload) {
  return {
    type: 'ADD',
    payload
  }
}

store.dispatch(add('MT'));
store.dispatch(add('Reci'));
...
subscribe() 方法

可以通过 <u>subscribe</u> 方法注册监听器(类似事件),每次 <u>dispatch</u> <u>action</u> 的时候都会执行监听函数,该方法返回一个函数,通过该函数可以取消当前监听器

let unsubscribe = sotre.subscribe(function() {
  console.log(store.getState());
});
unsubscribe();

Redux 工作流

[图片上传失败...(image-bba1ee-1614260835998)]

Reducers 分拆与融合

当一个应用比较复杂的时候,状态数据也会比较多,如果所有状态都是通过一个 <u>Reducer</u> 来进行修改的话,那么这个 <u>Reducer</u> 就会变得特别复杂。这个时候,我们就会对这个 <u>Reducer</u> 进行必要的拆分

let datas = {
  user: {},
  items: []
  cart: []
}

我们把上面的 <u>users</u>、<u>items</u>、<u>cart</u> 进行分拆

// user reducer
function user(state = {}, action) {
  // ...
}
// items reducer
function items(state = [], action) {
  // ...
}
// cart reducer
function cart(state = [], action) {
  // ...
}

<u>combineReducers</u> 方法

该方法的作用是可以把多个 <u>reducer</u> 函数合并成一个 <u>reducer</u>

let reducers = Redux.combineReducers({
  user,
  items,
  cart
});

let store = createStore(reducers);

react-redux

再次强调的是,<u>redux</u> 与 <u>react</u> 并没有直接关系,它是一个独立的 <u>JavaScript</u> 状态管理库,如果我们希望中 <u>React</u> 中使用 <u>Redux</u>,需要先安装 <u>react-redux</u>

<u>react-redux</u>

安装

npm i -S redux react-redux
// ./store/reducer/user.js
let user = {
    id: 0,
    username: ''
};

export default (state = user, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/users.js
let users = [{
    id: 1,
    username: 'baoge',
    password: '123'
},
{
    id: 2,
    username: 'MT',
    password: '123'
},
{
    id: 3,
    username: 'dahai',
    password: '123'
},
{
    id: 4,
    username: 'zMouse',
    password: '123'
}];

export default (state = users, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/items.js
let items = [
    {
        id: 1,
        name: 'iPhone XR',
        price: 542500
    },
    {
        id: 2,
        name: 'Apple iPad Air 3',
        price: 377700
    },
    {
        id: 3,
        name: 'Macbook Pro 15.4',
        price: 1949900
    },
    {
        id: 4,
        name: 'Apple iMac',
        price: 1629900
    },
    {
        id: 5,
        name: 'Apple Magic Mouse',
        price: 72900
    },
    {
        id: 6,
        name: 'Apple Watch Series 4',
        price: 599900
    }
];

export default (state = items, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/cart.js
export default (state = [], action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/index.js
import {createStore, combineReducers} from 'redux';

import user from './reducer/user';
import users from './reducer/users';
import items from './reducer/items';
import cart from './reducer/cart';

let reducers = combineReducers({
    user,
    users,
    items,
    cart
});

const store = createStore(reducers);

export default store;

<u>Provider</u> 组件

想在 <u>React</u> 中使用 <u>Redux</u> ,还需要通过 <u>react-redux</u> 提供的 <u>Provider</u> 容器组件把 <u>store</u> 注入到应用中

// index.js
import {Provider} from 'react-redux';
import store from './store';

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

<u>connect</u> 方法

有了 <u>connect</u> 方法,我们不需要通过 <u>props</u> 一层层的进行传递, 类似路由中的 <u>withRouter</u> ,我们只需要在用到 <u> store</u> 的组件中,通过 <u>react-redux</u> 提供的 <u>connect</u> 方法,把 <u>store</u> 注入到组件的 <u>props</u> 中就可以使用了

import {connect} from 'react-redux';

class Main extends React.Component {
  render() {
    console.log(this.props);
  }
}

export default connect()(Main);

默认情况下,<u>connect</u> 会自动注入 <u>dispatch</u> 方法

注入 <u>state</u> 到 <u>props</u>
export default connect( state => {
  return {
    items: state.items
  }
} )(Main);

<u>connect</u> 方法的第一个参数是一个函数

  • 该函数的第一个参数就是 <u>store</u> 中的 <u>state</u> : store.getState()
  • 该函数的返回值将被解构赋值给 <u>props</u> : this.props.items

Redux DevTools extension

为了能够更加方便的对 <u>redux</u> 数据进行观测和管理,我们可以使用 <u>Redux DevTools extension</u> 这个浏览器扩展插件

https://github.com/zalmoxisus/redux-devtools-extension

const store = createStore(
    reducers,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

打开浏览器开发者工具面板 -> redux

异步 action

许多时候,我们的数据是需要和后端进行通信的,而且中开发模式下,很容易就会出现跨域请求的问题,好在 <u>create-react-app</u> 中内置了一个基于 <u>node</u> 的后端代理服务,我们只需要少量的配置就可以实现跨域

package.json 配置

相对比较的简单的后端 <u>URL</u> 接口,我们可以直接中 <u>package.json</u> 文件中进行配置

// 后端接口
http://localhost:7777/api/items
// http://localhost:3000
{
  ...
  //
  "proxy": "http://localhost:7777"
}
axios({
  url: '/api/items'
});

./src/setupProxy.js 配置

针对相对复杂的情况,可以有更多的配置

npm i -S http-proxy-middleware
// setupProxy.js
const proxy = require('http-proxy-middleware');

module.exports = function(app) {
    app.use(
        proxy('/api', {
            target: 'http://localhost:7777/',
            pathRewrite: {
                '^/api': ''
            }
        })
    );
};

其它代码同上

Middleware

默认情况下,<u>dispatch</u> 是同步的,我们需要用到一些中间件来处理

const logger = store => next => action => {
  console.group(action.type)
  console.info('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  console.groupEnd(action.type)
  return result
}

redux.applyMiddleware

通过 <u>applyMiddleware</u> 方法,我们可以给 <u>store</u> 注册多个中间件

注意:<u>devTools</u> 的使用需要修改一下配置

npm i -D redux-devtools-extension
...
import { composeWithDevTools } from 'redux-devtools-extension';
...
const store = createStore( 
  reducres, 
  composeWithDevTools(
    applyMiddleware( logger )
  )
)

redux-thunk

这是一个把同步 <u>dispatch</u> 变成异步 <u>dispatch</u> 的中间件

安装

npm i -S redux-thunk
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';

import user from './reducer/user';
import items from './reducer/items';
import cart from './reducer/cart';

let reducers = combineReducers({
    user,
    items,
    cart
});

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

推荐阅读更多精彩内容