以前没太理解redux,在使用时老是照葫芦画瓢,看项目里别人如何使用,本身就如何使用,这一次完全学习了下官方文档,记录。css
在学习redux初时,有三个概念须要了解。react
类型是一个Object
更改store
中state
的惟一方法,它经过store.dispatch
将action
传到store
中git
一个简单的action
es6
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
复制代码
dispatch(addTodo(text))
复制代码
根据action
,来指定store中的state如何改变。github
存储stateshell
store.getState();
复制代码
getState()
方法获取statedispatch(action)
更新statesubscribe(listener)
来注册、取消监听器1.建立action,action中必需要有type 2.建立reducer,根据action中的type来更新store中的state 3.初始化storeexpress
在reducer更新state时,不能改变原有的state,只能从新建立一个新的state。这里提供了几个方法能够来建立一个不一样的对象。redux
以前并不了解immutable-js
,因此仍是使用es6的语法来执行不可变操做。segmentfault
let a = [1, 2, 3]; // [1, 2, 3]
let b = Object.assign([], a); // [1, 2, 3]
// a !== b
复制代码
上面和下面是相同的api
// es6语法
let a = [1, 2, 3]; // [1, 2, 3]
let b = [...a]; // [1, 2, 3]
// a !== b
复制代码
在建立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
复制代码
state
,给出当前的state
和action
state
, 你能够选择将其指定为通用应用程序中的服务器状态,或者还原之前序列化的用户会话,若是使用combineReducers
生成reducer
,则它必须是一个普通对象,其形状与传递给它的键相同。不然,您能够自由地传递reducer
只要可以理解。store
,例如中间件等等。随Redux
一块儿提供的enhancer
只有applyMiddleware()
,传入的enhancer只能是一个。(Store): 保存应用完整state
的对象,只要dispatching actions
才能改变它的state
。你能够用subscribe
它state
的改变来更新UI。
store
在一个应用当中,使用combineReducers
来建立根reducer
Immutable
,若是不肯定,先从普通对象开始reducers
返回对象时,不要使用Object.assign(state, newData)
,而是返回Object.assign({}, state, newData)
。这样就不会覆盖之前的状态,或者使用return {...state, ...newData}
enhancer
可使用compose()
,store
时,Redux
会发送一个虚拟的action
用来初始化store
的state
,初始化时第一个参数未定义,那么store的state会返回undefined
加强器
官方文档中有提到,中间件是用来包装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)
)
复制代码
须要额外安装
yarn add react-redux
复制代码
provider和connect必须一块儿使用,这样store
能够做为组件的props
传入。关于Provider
和connect
,这里有一篇淘宝的文章能够看下Provider和connect
大体使用以下,在root container
当中,会加入Provider
const App = () => {
return (
<Provider store={store}> <Comp/> </Provider>
)
};
复制代码
在根布局下的组件当中,须要使用到connect
。
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);
复制代码
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);
复制代码
import { setUser } from 'action';
// 在使用了connect的组件中 store在它的props当中
const { dispatch } = this.porps;
const user = ...;
// 直接分发设置user
dispatch(setUser(user));
复制代码
在没有使用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的类型是方法时,放回action
if (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对象。
在开始讲述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(
<Counter value={store.getState()} onIncrement={() => action('INCREMENT')} onDecrement={() => action('DECREMENT')} onIncrementAsync={() => action('INCREMENT_ASYNC')} />, document.getElementById('root') ) } 复制代码
在使用saga
时,都会创建一个saga.js
,其他的都是和普通的redux同样,须要建立action``reducer
和store
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
作出对store
的state
进行操做。
咱们能够看到这里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/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
建立一个关于user
的reducer
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 reducers
module.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">
<Head
ref="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">
<ControlList
ref="controlList"
onClickRefresh={this.onClickRefresh}
onClickAllStars={this.onClickAllStars}
onClickUntaggedStars={this.onClickUntaggedStars}
/>
</Col>
<Col span={5} className="star-list-container">
<StarList
tableData={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);
复制代码