以前沒太理解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);
複製代碼