作用:将想要全局共享的公共数据(类似static数据)通过state进行保存,只能通过action进行修改,修改后影响全局
store.getState( )
获取所有传入reducer中的state的一个整合对象,该对象在每一次dispatch之后会更新
function A(state=0, action:any) { switch(action.type) { case("ADD"): state += 1 return state default: return state } } function B(state={state:0}) { return state } let reducer = combineReducers({A, B}) // 最终state中显示的是什么属性名跟这里传进来的名字对应 let store = createStore(reducer) store.getState() // { A:0, B:{state:0} } store.dispatch({type:'ADD'}) store.getState() // { A:1, B:{state:0} }store.dispatch(action)
触发state变化的唯一途径,会使用当前getState()和传入的action以同步方式调用store的reduce函数,返回值会被作为下一个state,成为getState()的新返回值,同时变化监听函数(change listener)被触发,当dispatch 时,会触发所有的 reducer 函数,会根据指定的 action 条件触发对应的 reducer
dispatch()方法的的参数必须是一个对象,如果是一个方法则会报错,因此action是一个创建函数,要store.dispatch(action())
//从发送action到redux内的state更新这一过程是同步的(即dispatch是同步操作),如果只是简单观察显现可能呈现出异步的效果,这是因为React的setState异步导致的 // 能确保执行完dispatch(A)只会才开始执行dispatch(B),不需要额外加上await demo = anync () => { dispatch(A) await request.post(...) // 假设这里执行了一些异步操作 dispatch(B) }store.subscribe(listener)
每当执行store.dispatch(action)时便会自动执行此方法,需要操作当前state时可用store.getState()获取当前state
使用action的两种方法:
直接使用一个对象 const action = { type:'' } action创建函数,使用一个函数返回一个对象(约定传入参数放进payload属性里) const action = (prams) => { return { type:'' payload: { prams } } }通过触发store的dispatch方法,会触发reducer方法,reducer会拿到dispatch传入的action,再对action内的type属性值进行判断并采用对应方式更改state
这里触发的reducer实际上是 `store = createStore(reducer)` 中的reducer【使用combineReducers方法整合所有reducer】 而该reducer是所有reducer的集合 因此实际上当执行 `store.dispatch(atcion)` 时,会将action传入该集合,相当于触发了所有的reducer,然后执行满足条件的那一块语句reducer函数拥有两个参数:初始state和传入的action,当触发 dispatch(action)时,会将该action传递给reducer的第二个参数
const reducer = (initState, action) { ... }设计state结构
redux中所有state被保存在一个单一对象中,不同类型的state需要想办法进行区分
{ visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] }保证reducer纯净
不可在reducer里执行如下操作:
修改传入参数执行有副作用的参数,如API请求和路由跳转调用非纯函数,如Data.now()或Math.random(),这些函数每次调用返回的都是不同结果只要传入参数相同,返回计算得到的下一个state就一定相同,没有特殊情况,没有副作用,没有API请求,没有变量修改,单纯执行计算
reducer的最终目的只是接受一个条件并根据条件修改state,其他不是以修改state为目的的操作应该都在外部操作,如在action中将拿到的数据处理完毕后再将最终结果传递给reducer执行
Action
redux首次执行时,state为undefined,可另设置初始state
function todoApp(state = initialState, action) { // 这里暂不处理任何 action,仅返回传入的 state return state }方式一:统一管理reducer和action
因为每次 dispatch(action) 都会触发所有的 reducer,任意 action 也可以在任意一个组件的 dispatch 中使用,因此为了能方便看到所有的 action 和 reducer,可以统一写在一个文件里,同理 action 的 type 也可以全部写在一个文件里方便导入
目录结构
src | |__ actions | |__ index.js | |__ actionTypes.js | |__ reducers | |__ index.js | |__ store.js 管理reducers 在项目src目录下创建reducers文件夹,用于统一存放reducer文件,创建index.js文件,用于统一导入reducer文件并导出 import reducerA from './reducerA' import reducerB from './reducerB' import {combineReducers} from 'redux' // redux专门用来提供合并reducers的工具 export default combineReducers({ reducerA, // 相当于 reducerA: rerducerA reducerB }) /* 不可以直接导出,这样导出的不是一个方法,无法被识别 export default { reducerA, reducerB } */ 一开始进行reducer的编写时,只需写明state和声明一个简单方法导出即可,后续再加功能
const reducerA_state = { // ... } export default (state = reducerA_state, action) => { switch(action.type) { default: return state } }管理store
在项目的src目录下创建store.js文件(注意只能存在一个store)
import {createStore} from 'redux' import rootReducer from './reducers' export default createStore(rootReducer)管理actions
在项目src目录下创建actions文件夹,用于统一存放action文件,一般新建一个actionType.js文件统一对action的type进行管理并导出,然后另创建具体的action.js文件使用action创建函数的方式导出具体action
如:实现一个商品计数器的action
// actionType.js export default { CART_AMOUNT_INCREMENT: 'CART_AMOUNT_INCREMENT', CART_AMOUNT_DECREMENT: 'CART_AMOUNT_DECREMENT' } // /src/actions/cart.js import actionType from './actionType' export const increment = (id) =>{ return { type: actionType.CART_AMOUNT_INCREMENT, payload: { id } } } export const decrement = (id) => { return{ type: actionType.CART_AMOUNT_DECREMENT, payload: { id } } } 在reducer里导入的是actionType.js,在使用store.dispatch( )方法的组件中导入cart.js
// /src/reducers/cart.js import actionType from '../actions/actionType' const initState = [{ id: 1, title: 'Apple', price: 8888.66, amount: 10 },{ id: 2, title: 'Orange', price: 4444.66, amount: 12 }] export default (state = initState, action) => { switch(action.type) { case actionType.CART_AMOUNT_INCREMENT: return state.map(item => { if(item.id === action.payload.id) { item.amount += 1 } return item }) default: return state } } import store from '../../store' import {increment, decrement} from '../../actions/cart' // ... <button onClick = { () => { this.store.dispatch(decrement(id)) } }/> <button onClick = { () => { this.store.dispatch(increment(id)) } }/>方式二:按功能管理reducer和action
在 /src 下建立 store 目录,在该目录下新建一个 index.js 文件,用于创建 redux 和引入中间件
在 store 目录下根据功能创建各功能目录,在该目录内创建功能涉及到的 action 和 reducer 和 types
这样就能很明确的指定哪些功能有哪些 action 和 会触发哪些 reducer
不按组件划分的原因可能会有很多组件用到了同样的 reducer 和 action 功能,他们很多时候不只是服务于一个组件的,用组件的关系划分可能会存在很多重复
具体该如何分开这些功能则按具体项目要求决定,如一个小型网站只有用户和用户操作的功能 则可以分为两块,如 user 和 doing 文件 user文件:负责用户的登入注册登出等这些操作所需要的reducer和action功能 doing文件:负责用户的留言点赞转发等这些操作所需要的reducer和action功能目录结构
src | |__ store | |__ user | |__ actions.js | |__ reducer.js | |__ types.js | |__ index.jsreact-redux是redux对react的官方绑定库,借助react-redux可以很方便的在react中使用redux
react-redux需要配合redux来使用,redux对外暴露了以下方法,除了这些都是react-redux的内容
export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose, __DO_NOT_USE__ActionTypes }使用步骤:
通过createStore()方法创建store;
通过Provider组件将store注入到需要使用store的组件中;
通过connect()连接UI组件和容器组件,从而更新state和dispatch(action)
实际项目中,需要权衡是直接使用Redux还是用React-Redux
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)
UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑,如果一个组件既有 UI 又有业务逻辑,则将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件,前者负责与外部的通信,将数据传给后者,由后者渲染出视图
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成,也就是说,用户负责视觉层,状态管理则是全部交给它
UI组件
只负责 UI 的呈现,不带有任何业务逻辑没有状态(即不使用this.state这个变量)所有数据都由参数(this.props)提供不使用任何 Redux 的 API容器组件
负责管理数据和业务逻辑,不负责 UI 的呈现带有内部状态state使用 Redux 的 API UI组件容器组件作用描述如何展现(骨架、样式)描述如何运行(数据获取、状态更新)直接使用 Redux否是数据来源props监听 Redux state数据修改从 props 调用回调函数向 Redux 派发 actions调用方式手动通常由 React Redux 生成Provider
使用provider进行快捷组件交流,不需要多层传递数据
使用时需要将其包在要传递组件的最外层,由于<App/>为组件渲染根元素,因此任意子组件内都可直接使用provider传递的数据,无需层层相传,必须要要拥有store属性,值为创建的store
// /src/index.js import React from 'react' import { render } from 'react-dom' import App from './App' import store from './store' import {Provider} from 'react-redux' render( <Provider store={store}> <App/>, </Provider>, document.querySelector('#root') )connect
通过connect( )( )自动生成的容器组件(高阶组件),经过connect操作后会将dispatch方法传入该组件
使用了connect之后是自动订阅state的变化并进行重新渲染的,不需要再去通过store.subscribe(listener)去监听state的变化
import {connect} from 'react-redux' export default VisibleMyComponent = connect()(myComponent) // 一般合起来写,作用和下面的一样 /* const VisibleMyComponent = connect()(myComponent) export default VisibleMyComponent 使用装饰器的写法: @connect() // connect()()有两层括号,使用装饰器后会少一个 class myComponent {...} export default myComponent */使用connect后会在原来传进组件props的基础上增加内容
class componentA {} // props为传递进来的内容 @connect() class componentA {} // props为传递进来的内容和dispatch方法connect( )接受两个参数:mapStateToProps 和 mapDispatchToProps ,它们定义了 UI 组件的业务逻辑,前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action
它们主要是为了让redux使用更加方便而服务的,即使不设置也不影响其功能
const VisibleMyComponent = connect( mapStateToProps, mapDispatchToProps )(myComponent)mapStateToProps()
它是一个函数,返回一个对象,对象内的属性值(一般是state中的属性)会被加到组件的props中,为了更方便的获取到state值
当在connect绑定该函数时,对应的函数会自动传进来一个值,这个state为 store.getState() 的值
在组件执行render方法前被执行,因此每次render后的组件状态都与store同步
// 若原组件的props内容为{a:1} // 使用connect高阶组件化并设置mapStateToProps的组件props为{a:1, addPrpps:xxx, dispatch: function} const mapStateProps = (state, ownProps) => { // ownProps为传进该组件的的props return { addProps:state.myState } } mapDispatchToProps()它可以是一个函数,也可以是一个对象,相当于封装了一个传入固定action的dispatch方法并添加到组件的props,为了更方便地使用dispatch方法
在 connect() 传入该形参,则不会再将 dispatch 方法传入props,取而代之的是封装映射的方法
mapDispatchToProps()在组件constructor()中被执行,因而只执行一次
为一个函数时:当在connect绑定该函数会传入dispatch方法,然后手动封装一个触发固定action的dispatch函数,可以写死传入形参也可以不写入具体形参,具体使用时再传入
const increment = (id) =>{ return { type: actionType.CART_AMOUNT_INCREMENT, payload: { id } } } const decrement = (id) => { return{ type: actionType.CART_AMOUNT_DECREMENT, payload: { id } } } // 第一个参数用于接受store.dispatch()方法,第二个参数用于接受组件自身的props const mapDispatchToProps = (dispatch, ownProps) => { return { add: (id) => { dispatch(increment(id)) }, dec: (id) => { dispatch(decrement(id)) } } } // 封装后: // 使用 this.props.dispatch(increment(id)) 等同于使用 this.props.add(id) // 使用 this.props.dispatch(decrement(id)) 等同于使用 this.props.dec(id) 为一个对象时,键值内容应为action对象或action创建函数,会自动dispatch该对象中的所有属性(为创建函数时则是执行后的action结果)
const increment = (id) => { return { type: 'CART_AMOUNT_INCREMENT', payload: { id } } } const decrement = (id) => { return { type: 'CART_AMOUNT_DECREMENT', payload: { id } } } const mapDispatchToProps = { add: increment, add: decrement, } // this.props.add(id)一般是在action先写好了mapStateToProps和mapDispatchToProps再导出到组件并传到connect里
// 为了展示方便全写在一个文件里,实际在项目中需要分类各个文件 import React, { Component } from 'react' import ReactDOM from 'react-dom' import { createStore } from 'redux' import { Provider, connect } from 'react-redux' // 定义counter组件 class Counter extends Component { render() { const { value, onIncreaseClick } = this.props return ( <div> <span>{value}</span> <button onClick={onIncreaseClick}>自增按钮</button> </div> ) } } // Action const increaseAction = { type: 'increase' } // Reducer 基于原有state根据action得到新的state function counter(state = { count: 0 }, action) { switch (action.type) { case 'increase': return { count: state.count + 1 } default: return state } } // 根据reducer函数通过createStore()创建store const store = createStore(counter) // 将state映射到Counter组件的props function mapStateToProps(state) { return { value: state.count } } // 将action映射到Counter组件的props function mapDispatchToProps(dispatch) { return { onIncreaseClick: () => dispatch(increaseAction) } } // 传入上面两个函数参数,将Counter组件变为App组件 const App = connect( mapStateToProps, mapDispatchToProps )(Counter) ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )