内容简介:之前没太理解redux,在使用时总是照葫芦画瓢,看项目里别人如何使用,自己就如何使用,这一次彻底学习了下官方文档,记录。在学习redux初时,有三个概念需要了解。类型是一个
之前没太理解redux,在使用时总是照葫芦画瓢,看项目里别人如何使用,自己就如何使用,这一次彻底学习了下官方文档,记录。
在学习redux初时,有三个概念需要了解。
- action
- reducer
- store
Action
类型是一个 Object
更改 store
中 state
的唯一方法,它通过 store.dispatch
将 action
传到 store
中
一个简单的 action
function addTodo(text) { return { type: ADD_TODO, text } } 复制代码
dispatch(addTodo(text)) 复制代码
Reducer
根据 action
,来指定store中的state如何改变。
store
存储state
store.getState(); 复制代码
getState() dispatch(action) 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
,给出当前的state
和action
- preloadedState (Any,可选):初始化
state
, 你可以选择将其指定为通用应用程序中的服务器状态,或者还原以前序列化的用户会话,如果使用combineReducers
生成reducer
,则它必须是一个普通对象,其形状与传递给它的键相同。否则,您可以自由地传递reducer
只要能够理解。 - enhancer (Function,可选),可以指定它使用第三方功能增强
store
,例如中间件等等。随Redux
一起提供的enhancer
只有applyMiddleware()
,传入的enhancer只能是一个。
返回值
(Store): 保存应用完整 state
的对象,只要 dispatching actions
才能改变它的 state
。你可以用 subscribe
它 state
的改变来更新UI。
Tips
- 最多创建一个
store
在一个应用当中,使用combineReducers
来创建根reducer
- 你可以选择状态的格式,可以选择普通对象或类似
Immutable
,如果不确定,先从普通对象开始 - 如果state是个普通对象,请确定永远不要改变它,例如从
reducers
返回对象时,不要使用Object.assign(state, newData)
,而是返回Object.assign({}, state, newData)
。这样就不会覆盖以前的状态,或者使用return {...state, ...newData}
- 要使用多个
enhancer
可以使用compose()
, - 创建
store
时,Redux
会发送一个虚拟的action
用来初始化store
的state
,初始化时第一个参数未定义,那么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
传入。关于 Provider
和 connect
,这里有一篇淘宝的文章可以看下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的类型是方法时,放回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对象。
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( <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
- 建立对应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
创建一个关于 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); 复制代码
以上所述就是小编给大家介绍的《redux 入门到实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective Java: Second Edition
Joshua Bloch / Addison-Wesley / 2008-05-28 / USD 54.99
Written for the working Java developer, Joshua Bloch's Effective Java Programming Language Guide provides a truly useful set of over 50 best practices and tips for writing better Java code. With plent......一起来看看 《Effective Java: Second Edition》 这本书的介绍吧!