内容简介:之前没太理解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 入门到实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
高效前端:Web高效编程与优化实践
李银城 著 / 机械工业出版社 / 2018-3-15 / 89.00元
这不是一本单纯讲解前端编程技巧的书,而是一本注重思想提升和内功修炼的书。 全书以问题为导向,精选了前端开发中的34个疑难问题,从分析问题的原因入手,逐步给出解决方案,并分析各种方案的优劣,最后针对每个问题总结出高效编程的最佳实践和各种性能优化的方法。 全书共7章,内容从逻辑上大致可以分为两大类: 第一类,偏向实践,围绕HTML、CSS、JavaScript等传统前端技术,以及PW......一起来看看 《高效前端:Web高效编程与优化实践》 这本书的介绍吧!