前言

之前没太理解redux,在使用时总是照葫芦画瓢,看项目里别人如何使用,自己就如何使用,这一次彻底学习了下官方文档,记录。

在学习redux初时,有三个概念需要了解。

  • action
  • reducer
  • store

Action

类型是一个Object 更改storestate的唯一方法,它通过store.dispatchaction传到store

一个简单的action

function addTodo(text) {return {type: ADD_TODO,text}
}
复制代码
dispatch(addTodo(text))
复制代码

Reducer

根据action,来指定store中的state如何改变。

store

存储state

store.getState();
复制代码
  • 提供getState()方法获取state
  • 提供dispatch(action)更新state
  • subscribe(listener)来注册、取消监听器

更新store的步骤

1.创建action,action中必须要有type 2.创建reducer,根据action中的type来更新store中的state 3.初始化store

理解不可变性

在reducer更新state时,不能改变原有的state,只能重新创建一个新的state。这里提供了几个方法可以来创建一个不同的对象。

  • 使用immutable-js创建不可变的数据结构
  • 使用JavaScript库(如Loadsh)来执行不可变的操作
  • 使用ES6语法执行不可变操作

之前并不了解immutable-js,所以还是使用es6的语法来执行不可变操作。

let a = [1, 2, 3];              // [1, 2, 3]
let b = Object.assign([], a);   // [1, 2, 3]// a !== b
复制代码

上面和下面是相同的

// es6语法
let a = [1,  2, 3]; // [1, 2, 3]
let b = [...a];     // [1, 2, 3]// a !== b
复制代码

初始化store

在创建store时要将注意传入开发者工具相关参数

import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger'
import api from '../middleware/api'
import rootReducer from '../reducers'
import DevTools from '../containers/DevTools'const configureStore = preloadedState => {const store = createStore(rootReducer,preloadedState,compose(applyMiddleware(thunk, api, createLogger()),DevTools.instrument()))// ..省略相关代码return store
}export default configureStore
复制代码

createStore

参数

  • reducer (Function,必选):用于返回新的state,给出当前的stateaction
  • preloadedState (Any,可选):初始化state, 你可以选择将其指定为通用应用程序中的服务器状态,或者还原以前序列化的用户会话,如果使用combineReducers生成reducer,则它必须是一个普通对象,其形状与传递给它的键相同。否则,您可以自由地传递reducer只要能够理解。
  • enhancer (Function,可选),可以指定它使用第三方功能增强store,例如中间件等等。随Redux一起提供的enhancer只有applyMiddleware(),传入的enhancer只能是一个。

返回值

(Store): 保存应用完整state的对象,只要dispatching actions才能改变它的state。你可以用subscribestate的改变来更新UI。

Tips

  • 最多创建一个store在一个应用当中,使用combineReducers来创建根reducer
  • 你可以选择状态的格式,可以选择普通对象或类似Immutable,如果不确定,先从普通对象开始
  • 如果state是个普通对象,请确定永远不要改变它,例如从reducers返回对象时,不要使用Object.assign(state, newData),而是返回Object.assign({}, state, newData)。这样就不会覆盖以前的状态,或者使用return {...state, ...newData}
  • 要使用多个enhancer可以使用compose()
  • 创建store时,Redux会发送一个虚拟的action用来初始化storestate,初始化时第一个参数未定义,那么store的state会返回undefined

Enhancer

增强器

Middleware

官方文档中有提到,中间件是用来包装dispatch

这里看一个官方的例子,从这个例子中就可以看到,传入参数是action,随后可以对这个action进行一些操作。

import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'function logger({ getState }) {return next => action => {console.log('will dispatch', action)// Call the next dispatch method in the middleware chain.const returnValue = next(action)console.log('state after dispatch', getState())// This will likely be the action itself, unless// a middleware further in chain changed it.return returnValue}
}const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))store.dispatch({type: 'ADD_TODO',text: 'Understand the middleware'
})
// (These lines will be logged by the middleware:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]
复制代码

使用applyMiddleware参数可以使多个中间件,最后返回的是一个enhancer

相关提示
  • 有一些中间件可能只在某个特定环境下使用,比如日志中间件,可能在生成环境就不需要了。需要注意引用。
let middleware = [a, b]
if (process.env.NODE_ENV !== 'production') {const c = require('some-debug-middleware')const d = require('another-debug-middleware')middleware = [...middleware, c, d]
}const store = createStore(reducer,preloadedState,applyMiddleware(...middleware)
)
复制代码

Provider与connect

需要额外安装

yarn add react-redux
复制代码

provider和connect必须一起使用,这样store可以作为组件的props传入。关于Providerconnect,这里有一篇淘宝的文章可以看下Provider和connect

大致使用如下,在root container当中,会加入Provider

const App = () => {return (<Provider store={store}><Comp/></Provider>)
};
复制代码

在根布局下的组件当中,需要使用到connect

mapStateToProps

connect方法第一个参数mapStateToProps是可以将store中的state变换为组件内部的props来使用。

const mapStateToProps = (state, ownProps) => {// state 是 {userList: [{id: 0, name: '王二'}]}// 将user加入到改组件中的props当中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);复制代码

mapDispatchToProps

connect方法的第二个参数,它的功能是将action作为组件的props

const mapDispatchToProps = (dispatch, ownProps) => {return {increase: (...args) => dispatch(actions.increase(...args)),decrease: (...args) => dispatch(actions.decrease(...args))}
}class MyComp extends Component {render(){const {count, increase, decrease} = this.props;return (<div><div>计数:{this.props.count}次</div><button onClick={increase}>增加</button><button onClick={decrease}>减少</button></div>)}
}const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp);复制代码

利用props使用store

import { setUser } from 'action';
// 在使用了connect的组件中 store在它的props当中
const { dispatch } = this.porps;const user = ...;
// 直接分发设置user
dispatch(setUser(user));
复制代码

异步场景下更新store

  • Thunk middleware
  • redux-promise
  • redux-observable
  • redux-saga
  • redux-pack
  • 自定义...

Redux-thunk

在没有使用Redux-thunk之前,当我们需要改变store中的state,只能使用使用dispath传入action的形式,这里有个官方的例子能够说明它的使用场景。

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';// Note: this API requires redux@>=3.1.0
const store = createStore(rootReducer,applyMiddleware(thunk)
);function fetchSecretSauce() {return fetch('https://www.google.com/search?q=secret+sauce');
}// These are the normal action creators you have seen so far.
// The actions they return can be dispatched without any middleware.
// However, they only express “facts” and not the “async flow”.function makeASandwich(forPerson, secretSauce) {return {type: 'MAKE_SANDWICH',forPerson,secretSauce};
}function apologize(fromPerson, toPerson, error) {return {type: 'APOLOGIZE',fromPerson,toPerson,error};
}function withdrawMoney(amount) {return {type: 'WITHDRAW',amount};
}// Even without middleware, you can dispatch an action:
store.dispatch(withdrawMoney(100));// But what do you do when you need to start an asynchronous action,
// such as an API call, or a router transition?// Meet thunks.
// A thunk is a function that returns a function.
// This is a thunk.function makeASandwichWithSecretSauce(forPerson) {// Invert control!// Return a function that accepts `dispatch` so we can dispatch later.// Thunk middleware knows how to turn thunk async actions into actions.return function (dispatch) {return fetchSecretSauce().then(sauce => dispatch(makeASandwich(forPerson, sauce)),error => dispatch(apologize('The Sandwich Shop', forPerson, error)));};
}// Thunk middleware lets me dispatch thunk async actions
// as if they were actions!store.dispatch(makeASandwichWithSecretSauce('Me')
);// It even takes care to return the thunk’s return value
// from the dispatch, so I can chain Promises as long as I return them.store.dispatch(makeASandwichWithSecretSauce('My wife')
).then(() => {console.log('Done!');
});
复制代码

thunk可以让我们在dispatch执行时,可以传入方法,而不是原本的action

我们可以看一下thunk的源码,当action是方法时,它会将action进行返回。

function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => next => action => {// action的类型是方法时,放回actionif (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);};
}const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;export default thunk;
复制代码

经过这样,我们就可以理解为什么在上述的官方例子当中可以这么使用。

store.dispatch(makeASandwichWithSecretSauce('My wife')
).then(() => {console.log('Done!');
});
复制代码

makeASandwichWithSecretSauce实际会返回fetch().then()返回值,而fetch().then()返回的是Promise对象。

Redux-saga

在开始讲述saga以前,先讲下与它相关的ES6语法 Generator函数

function* helloWorldGenerator() {// 可以将yield看成return,只不过yield时,还能继续yield 'hello';yield 'world';return 'ending';
}var hw = helloWorldGenerator();hw.next()
// { value: 'hello', done: false }hw.next()
// { value: 'world', done: false }hw.next()
// { value: 'ending', done: true }hw.next()
// { value: undefined, done: true }
复制代码

异步Generator函数

这里有2个方法,一个是通过回调写的,一个是通过generator来写的

fs.readFile('/etc/passwd', 'utf-8', function (err, data) {if (err) throw err;console.log(data);
});
复制代码
function* asyncJob() {// ...其他代码var f = yield readFile(fileA);// ...其他代码
}
复制代码

官方文档的一个例子如下

function render() {ReactDOM.render(<Countervalue={store.getState()}onIncrement={() => action('INCREMENT')}onDecrement={() => action('DECREMENT')}onIncrementAsync={() => action('INCREMENT_ASYNC')} />,document.getElementById('root'))
}
复制代码

在使用saga时,都会建立一个saga.js,其余的都是和普通的redux一样,需要创建action``reducerstore

import { delay } from 'redux-saga'
import { put, takeEvery } from 'redux-saga/effects'// ...// Our worker Saga: 将执行异步的 increment 任务
export function* incrementAsync() {yield delay(1000)yield put({ type: 'INCREMENT' })
}// Our watcher Saga: 在每个 INCREMENT_ASYNC action spawn 一个新的 incrementAsync 任务
export function* watchIncrementAsync() {yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}
复制代码

当主动触发了onIncrementAsync回调之后,就会发送一个INCREMENT_ASYNC,在saga接受到这个action时候,就会incrementAsync,在这个方法当中会延迟1000毫秒,随后put(类似于dispatch)发送一个type为increment的事件,在reducer当中,可以根据这个action做出对storestate进行操作。

我们可以看到这里yield的使用更像是await。

两种其实都是通过不同的异步方式对store进行操作。thunk本身其实没有异步的功能,但是它能够拓展dispath,加入传入的是一个异步方法,那就让它能够具有异步的功能。

设置开发者工具

在官方Example当中有提到,创建一个DevTools文件,ctrl-h打开显示toggle,ctrl-w改变开发者工具的位置

import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'export default createDevTools(<DockMonitor toggleVisibilityKey="ctrl-h"changePositionKey="ctrl-w"><LogMonitor /></DockMonitor>
)复制代码

然后将该组件放在根目录

import React from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import DevTools from './DevTools'
import { Route } from 'react-router-dom'
import App from './App'
import UserPage from './UserPage'
import RepoPage from './RepoPage'const Root = ({ store }) => (<Provider store={store}><div><Route path="/" component={App} /><Route path="/:login/:name"component={RepoPage} /><Route path="/:login"component={UserPage} /><DevTools /></div></Provider>
)Root.propTypes = {store: PropTypes.object.isRequired,
}export default Root复制代码

最后在createStore时需要传入

import DevTools from '../devtool'const store = createStore(rootReducer,preloadedState,compose(applyMiddleware(thunk),DevTools.instrument()))
复制代码

效果图如下

实战

我们需要的要使用redux需要

  • 建立action
  • 建立对应reducer
  • 创建store

同时,为了方便

  • 需要有Provider

项目目录

项目目录如下所示

action/index.js

创建一个action,用于告知reducer,设置用户信息,增加一个type,让reducer根据type来更新store中的state

export const TYPE = {SET_USER: 'SET_USER'
};export const setUser = (user) => ({type: 'SET_USER',user
});
复制代码

reducer/user.js

创建一个关于userreducer

import {TYPE
} from '../action'const createUser = (user) => user;const user = (state = {}, action) => {console.log(action);switch (action.type) {case TYPE.SET_USER:// 根据type来更新用户信息return {...state, ...createUser(action.user)};default:return state;}
}export {user
}复制代码

reducers/index.js

reducer,用于将其他不同业务的reducer合并。

import { combineReducers } from 'redux';import { user } from './user';export default combineReducers({user
});
复制代码

store/config-store.dev.js

store中有不同的初始化store的方法,dev中有开发者工具,而pro中没有。这里做了个区分。

import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'
import DevTools from '../devtool'const configureStore = preloadedState => {const store = createStore(rootReducer,preloadedState,compose(applyMiddleware(thunk),DevTools.instrument()))if (module.hot) {// Enable Webpack hot module replacement for reducersmodule.hot.accept('../reducers', () => {store.replaceReducer(rootReducer)})}return store
}export default configureStore复制代码

store/configure-store.prod.js

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'const configureStore = preloadedState => createStore(rootReducer,preloadedState,applyMiddleware(thunk)
)export default configureStore
复制代码

store/configure-store.js

根据不同环境读取不同的初始化store的文件。

if (process.env.NODE_ENV === 'production') {module.exports = require('./configure-store.prod')
} else {module.exports = require('./configure-store.dev')
}复制代码

devtool/index.js

开发者组件的配置文件。

import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'export default createDevTools(<DockMonitor toggleVisibilityKey="ctrl-h"changePositionKey="ctrl-w"><LogMonitor /></DockMonitor>
)复制代码

index.js

在index.js中初始化store

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import configureStore from './store/store/configure-store';const store = configureStore();ReactDOM.render(<App store={store}/>, document.getElementById('root'));
registerServiceWorker();复制代码

app.jsx

在根文件中,创建provider

import React, { Component } from 'react'
import './App.css'
import './reset.css'
import 'antd/dist/antd.css'
import Auth from './pages/auth'
import Star from './pages/star/star'
import { BrowserRouter, Route, Redirect } from 'react-router-dom'
import DevTools from './store/devtool'
import { Provider } from 'react-redux'class App extends Component {constructor(props) {super(props)this.onClickAuth = this.onClickAuth.bind(this)}onClickAuth() {}/*** 渲染开发者工具*/renderDevTools() {if (process.env.NODE_ENV === 'production') {return null;}return (<DevTools />)}render() {return (<Provider store={this.props.store}><div className="App"><BrowserRouter basename="/"><div><Route exact path="/" component={Auth} /><Route path="/auth" component={Auth} /><Route path="/star" component={Star} />{ this.renderDevTools() }</div></BrowserRouter></div></Provider>)}
}export default App复制代码

更新用户信息

import React, { Component } from 'react';
import './star.scss';
import globalData from '../../utils/globalData';
import StringUtils from '../../utils/stringUtils';
import { List, Avatar, Row, Col } from 'antd';
import Api from '../../utils/api';
import Head from '../../components/Head/Head';
import ResInfo from '../../components/resInfo/resInfo';
import ControlList from '../../components/control/control-list';
import StarList from '../../components/star-list/star-list';
import Eventbus from '@/utils/eventbus.js';
import { connect } from 'react-redux';
import { setUser } from '../../store/action';class Star extends Component {constructor(props) {super(props);this.state = {tableData: [],originTableData: [],userInfo: {},rawMdData: ''};}componentDidMount() {this.getUserInfo();}componentWillUnmount() {}getUserInfo() {Api.getAuthenticatedUser().then(data => {this.handleGetUserInfoSuccessResponse(data);}).catch(e => {console.log(e);});}/*** 获取完用户信息*/handleGetUserInfoSuccessResponse(res) {this.setState({userInfo: res.data});this.getStarFromWeb();this.refs.controlList.getTagsFromWeb();const { dispatch } = this.props;// 更新用户信息dispatch(setUser(this.state.userInfo));}// ...省略一些代码render() {return (<div className="star"><Headref="head"head={this.state.userInfo.avatar_url}userName={this.state.userInfo.login}/><Row className="content-container"><Col span={3} className="control-list-container bg-blue-darkest"><ControlListref="controlList"onClickRefresh={this.onClickRefresh}onClickAllStars={this.onClickAllStars}onClickUntaggedStars={this.onClickUntaggedStars}/></Col><Col span={5} className="star-list-container"><StarListtableData={this.state.tableData}onClickResItem={this.onClickResItem.bind(this)}/></Col><Col span={16}><div className="md-container"><ResInfo resSrc={this.state.rawMdData} /></div></Col></Row></div>);}
}const mapStateToProps = (state, ownProps) => ({user: state.user
});export default connect(mapStateToProps)(Star);复制代码

学习文章

  • Redux 入门
  • Redux examples
  • Redux入门教程

转载于:https://juejin.im/post/5c0e3ff6f265da61553aa8b6

redux 入门到实践相关推荐

  1. redux入门_Redux入门

    redux入门 典型的Web应用程序通常由几个共享数据的UI组件组成. 通常,多个组件的任务是显示同一对象的不同属性. 该对象表示可以随时更改的状态. 在多个组件之间保持状态一致可能是一场噩梦,尤其是 ...

  2. 计算机编程书籍-Python硬件开发树莓派从入门到实践无人驾驶 AndroidTV 自动循迹

    内容简介 <Python硬件开发树莓派从入门到实践>全书可大致分为4个部分,其中第13章介绍了树莓派的基本使用:第4章介绍了如何在树莓派上使用Python的OpenCV包:第58章介绍了树 ...

  3. 免费送书啦!《 OpenCV图像处理入门与实践》一本全搞定

    OpenCV 的基础图像操作都只是针对图像中的像素点,并不是直接对图像整体进行的操作.而很多时候并不能仅通过改变像素点来进行图像的操作,为此我们需要学习关于图像的算术操作. 1.图像加法 对于两张相同 ...

  4. python快速编程入门课后简答题答案-Python编程:从入门到实践(第2版)第1章习题答案...

    <Python编程:从入门到实践>是一本不错的书.第2版已经公开预售,预计会在10月份正式上市. 动手试一试 本章的练习都是探索性的,但从第2章开始将要求你用那一章学到的知识来解决问题. ...

  5. python入门到实践-一本书搞定Python入门到实践

    上周介绍了几本Python从入门到进阶书籍,今天继续推荐好书,这本书是<Python编程:从入门到实践>.入门 Python 最好的书之一,适合零基础小白,也适合有其它语言背景的程序员 书 ...

  6. python如何导入txt数据集-终于找到python入门到实践数据集

    Python是一款功能强大的脚本语言,具有丰富和强大的库,重要的是,它还具有很强的可读性,易用易学,非常适合编程初学者入门.以下是小编为你整理的python入门到实践数据集 环境配置:下载Python ...

  7. python编程 从入门到实践怎么样-python编程从入门到实践这本书怎么样

    <Python编程-从入门到实践>作者: Eric Matthes,已翻译为中文,人民邮电出版社出版. python编程从入门到实践怎么样? 我们一起看看已经学习的同学对这本书的口碑和评价 ...

  8. python编程入门到实践 百度云-python网络爬虫从入门到实践pdf

    python网络爬虫从入门到实践pdf是一本非常热门的编程教学.这本书籍详细讲解了Python以及网络爬虫相关知识,非常适合新手阅读,需要的用户自行下载吧. Python网络爬虫从入门到实践电子书介绍 ...

  9. python编程入门指南怎么样-python编程从入门到实践这本书怎么样

    <Python编程-从入门到实践>作者: Eric Matthes,已翻译为中文,人民邮电出版社出版. python编程从入门到实践怎么样? 我们一起看看已经学习的同学对这本书的口碑和评价 ...

最新文章

  1. vsearch2.8.1使用和命令简介——中文帮助文档(免费64位版usearch)
  2. 简单分解帮助看清复杂问题
  3. Maven用仓库外的jar进行编译
  4. php项目课题,php课题
  5. 【C语言】三子棋游戏
  6. 基于Bootstrap的Asp.net Mvc 分页的实现(转)
  7. 我的六年软件测试感悟... 测试人的一生。算是摸透了软件测试这一行
  8. wordpress插件列表
  9. nexus build docker private registry
  10. c# 系列 - 基本知识
  11. CC2530无线点灯
  12. social-share,社会化分享组件之jquery版
  13. selenium chromedriver 无头浏览器检测
  14. 祝贺 StreamX 开源一周年
  15. nginx中location匹配规则与proxy_pass代理转发
  16. MySQL MGR搭建过程中常遇见的问题及解决办法
  17. 别用XShell了,这款SSH工具绝对惊艳,还支持网页版...
  18. 怎么连接PI数据库连接
  19. UID_AI_01_大师之路
  20. 掷骰子游戏成为百万富翁了解一下!

热门文章

  1. Redis实现分布式锁全局锁—Redis客户端Redisson中分布式锁RLock实现
  2. 利用nginx-status监控nginx服务器状态
  3. 关于cocos2d的下载和安装
  4. 组件化开发,制作Cocoapods Git库
  5. 代码雨代码源复制_超火!黑客帝国代码雨
  6. lzma打包exe_Web 项目打包EXE
  7. python datetime计算时间差_Python中关于日期的计算总结
  8. 脑机交互可提高行动能力
  9. Steve J. Luck推出最新ERPs独家视频课程
  10. Ultrahaptics公司为Holodeck型触觉关闭了2300万美元的资金回合