目录
redux
流程图介绍
redux 是一种数据状态管理模式,就相当于 vuex ,当我们要做的项目中有很多组件需要共享数据时,这时候就可以用 redux 搭建。
Redux的三个原则:
- 唯一数据源
- 保持状态只读(只能通过调用dispach的形式来修改状态),
- 数据改变只能通过纯函数(reducer)完成
redux包含3个比较重要的结构:store reducers actionCreators
创建store
const store = createStore(Reducers)
src/store/index.js
这里先把把reducer(cournter)和store放到一块,作为简单测试
import { createStore} from 'redux'
//两个参数分别是state,action
const counter =(state = 0,action)=>{
switch(action.type){
case'INCREMENT':
return state + 1
case'DECREMENT':
return state - 1
default:
return state
}
}
const store = createStore(counter)
export default store
src/index.js
注意:store.subscribe()
是 Redux 中的一个函数,用于订阅 store 的变化。当 store 中的 state 发生变化时,订阅函数会被调用。一般情况下,我们可以在订阅函数里调用 store.getState()
来获取最新的 state,然后根据最新的 state 进行相应的操作。
这里采用的是function 形式的render(),方便store.subscribe(render)接收一个函数
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './store'
function render() {
ReactDOM.render(<App />, document.getElementById('root'))
}
render()
//注意subscribe() 返回一个函数用来注销监听器
store.subscribe(render)
component/Counter.js
获取数据,store.getState
import React, { Component } from 'react'
import store from '../store'
class Counter extends Component {
render() {
return (
<div>
当前数值:{store.getState()}
<button onClick={() => store.dispatch({type:'INCREMENT'})}>+1</button>
<button onClick={() => store.dispatch({type:'DECREMENT'})}>-1</button>
</div>
)
}
}
export default Counter
最后在App.js中挂载子组件Count,
运行:
redux和react-redux 的关系
首先,redux 是独立的,和react没有什么关系,想要在react中使用redux需要进行一些列的操作把它们连接起来,这一些列的操作即为:react-redux
为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux
那么问题来了,既然这样就已经实现了redux,那还需要react-redux干啥?
思考:如果我要在其他页面也操作这个store,要怎么做?
redux通过编写一些列的代码可以实现这个功能,但是代码量比较大逻辑比较复杂
react-redux 中提供了provider和connect来解决这种问题
react-redux
connect与provider
connect 和 provider 是实现store数据共享的关键
connect
作用:提交数据,订阅数据变化的能力
使用方式:connect(mapStateToProps, mapDispatchToProps)(Conponents)
在下面的介绍的实现方式中还涉及到组件通信context,而connet 属于Consumer接收的一方
provider
provider作为提供store数据的一方,
同时封装高阶函数取代了redux的subscribe(render)形式
先看下如何使用
reducer
之前我们是把counter和store写在了一起。现在给他单独一个文件counter.js专门处理修改状态
src/reducer/counter.js
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// 处理状态方法
export const mapStateToProps = (state) => {
console.log(state)
return {
// 什么情况下添加reducer的标识 ,当使用combineReducers方法组合reducer
count: state.counter,
}
}
// 处理action事件 dispatch
export const mapDispatchToProps = (dispatch) => {
return {
increment() {
// dispatch({}) 表示同步操作,如果是个函数,则是异步
dispatch({ type: 'INCREMENT' })
},
decrement() {
dispatch({ type: 'DECREMENT' })
},
}
}
export default counter
在上面的例子中增加了两个暴露的函数mapStateToProps 、mapDispatchToProps
其中mapStateToProps是用来处理state状态,mapDispatchToProps 用来处理action事件,每个action事件都使用了dispatch方式。action事件还包含异步事件,后面会详细介绍。
src/store/index.js
在这里我们将创建好的reducer(counter.js)import引入。store的创建方式依旧是createStore()但是,我们使用的一个新的方法,combineReducers({reducer}).这个函数的作用是将所有的reducer统一整合起来。因为每个实例都只有一个store。而reducer操作不会只有一个。
在上面的reducer 创建当中。我们在处理state的时候。我们使用了标识,来给这些reducer进行区分。例如:这个reducer叫做counter,那我们就在后面加个.counter来区分。count: state.counter,那么在combinReducers的时候就将state中的counter标识作为render 标识
import { createStore, combineReducers, applyMiddleware } from 'redux'
import counter from '../reducer/counter'
//import thunk from 'redux-thunk'
//import logger from 'redux-logger'
const store = createStore(
combineReducers({ counter }),
//applyMiddleware(logger, thunk)
)
export default store
src/index.js
这里我们用provider 取代了原来的函数式render,然后将render置入subscribe()的方式
需要注意到的是provider的属性是store而不是context中的value,不要搞混了
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import { Provider,connect } from 'react-redux'
import App from './App'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
src/components/Counter.js
在导出的时候,我们使用了connect函数,并将引入的修改状态方法放入,然后传入组件Counter
import React, { Component } from 'react'
import store from '../store'
import { connect } from 'react-redux'
import { mapStateToProps, mapDispatchToProps } from '../reducer/counter'
class Counter extends Component {
render() {
const { count, increment, decrement} = this.props
return (
<div>
当前数值:{count}
<button onClick={() => increment()}>+1</button>
<button onClick={() => decrement()}>-1</button>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
看运行,和之前的redux方式写出来的效果是一样的
那么connect和provider都是如何实现的呢?
实现connect+provider
要实现就要使用到上一章学到的组件通信Context以及高阶函数封装
我们先在src目录下创建一个文件夹react-redux,在这个文件夹下创建connect.js和provider.js文件
首先看看connnect的实现:
// connect(mapStateToProps,mapDiapatchToProps)(Comp)
// 消费者
import React, { PureComponent } from 'react'
import { StoreContext } from './context'
// 提交数据,订阅数据变化的能力
const connect = (mapStateToProps, mapDispatchToProps) => {
return function enhanceComponent(WrapperComponent) {
class EnhanceComponet extends PureComponent {
static contextType = StoreContext
constructor(props) {
super(props)
// 组件依赖的state
this.state = {
storeState: null,
}
}
componentDidMount() {
// 获取store 观察者
const store = this.context
this.unsubscibe = store.subscribe(() => {
this.setState({
storeState: mapStateToProps(store.getState()),
})
})
}
componentWillUnmount() {
this.unsubscibe()
}
render() {
return (
<WrapperComponent
{...this.props}
{...mapStateToProps(this.context.getState())}
{...mapDispatchToProps(this.context.dispatch)}
></WrapperComponent>
)
}
}
return EnhanceComponet
}
}
export default connect
解析:
- 在connect 的实现中,为了store数据实现共享互通,采用了创建Context通信的方式,在文件夹下额外创建了一个context文件夹,在文件夹下创建index.js文件用于context共享。
- 通过传入两个修改方法,将组件进行高阶组件修饰然后返回。
- 通过extends PureComponents来进行浅比较,避免无效渲染。
- 在组件完成挂载时,将store.subscribe()用于订阅 store 的变化。当 store 中的 state 发生变化时,订阅函数会被调用。
- 在即将卸载组件调用unsubscribe()进行注销。最后将所有的数据都挂在组件参数上。
react-redux/context/index.js
注意:这里使用export导出,进行数据解构
import React from 'react'
export const StoreContext = React.createContext()
//使用解构的方式进行导出
Provider.js
import React, { Component } from 'react'
import { StoreContext } from './context'
class Provider extends Component {
constructor(props) {
super(props)
this.store = props.store
}
render() {
return (
<StoreContext.Provider value={this.store}>
{/* 匿名插槽 */}
{this.props.children}
</StoreContext.Provider>
)
}
}
export default Provider
注意,这里是实现provider采用的是通信组件Context的provider提供者,所以用的是value={}
再来看看之前的index.js
这里面是将导入的store作为props传入,然后共享给connect,this.porps.children也给App留位置
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import { Provider } from './react-redux'
import App from './App'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
看看运行:可以看到效果跟之前的react-redux是一样的
对了,把之前的异步操作加入进来;
不过首先要提到两个插件
redux中间件
利用redux中间件机制可以在实际action响应前执行其它额外的业务逻辑。
特点:自由组合,自由插拔的插件机制
使用:applyMiddleware(logger, thunk)
store/index.js
import { createStore, combineReducers, applyMiddleware } from 'redux'
import counter from '../reducer/counter'
import thunk from 'redux-thunk'
import logger from 'redux-logger'
// 创建reducer 具体状态执行者
const store = createStore(
combineReducers({ counter }),
applyMiddleware(logger, thunk)
)
export default store
将异步行为加入到reducers(counter.js)当中
如果是异步,就会以函数的形式进行dispatch
// 异步的行为
const asyncIncrement = () => {
return (dispatch) => {
setTimeout(() => {
dispatch({ type: 'INCREMENT' })
}, 1000)
}
}
// 处理action事件 dispatch
export const mapDispatchToProps = (dispatch) => {
return {
increment() {
// dispatch({}) 表示同步操作,如果是个函数,则是异步
dispatch({ type: 'INCREMENT' })
},
decrement() {
dispatch({ type: 'DECREMENT' })
},
asyncIncrement() {//异步操作
dispatch(asyncIncrement())
},
}
}
compontents/Counter.js
加入异步操作按钮
class Counter extends Component {
render() {
const { count, increment, decrement, asyncIncrement } = this.props
return (
<div>
当前数值:{count}
<button onClick={() => increment()}>+1</button>
<button onClick={() => decrement()}>-1</button>
<button onClick={() => asyncIncrement()}>异步+1</button>
</div>
)
}
}
查看运行:
当进行同步dispatch时,日志logger自动打印
当进行异步dispatch时
日志很详细,甚至连时间都能打印,是不是很好用呢?