Redux
安装
npm i redux -S
类似于vuex的状态托管插件库
使用
import { createStore } from 'redux'
// 1.定义reducer 相当于mutation
// 必须是同步的 因为必须要有return 一个新的state 不是修改state
// 数组新增
// return [
// ...state,
// action.payload
// ]
// 数组删除
// return [
// ...state.slice(0,action.index),
// ...state.slice(action.index+1)
// ]
// 切换某一项
// return [
// ...state.slice(0,action.index),
// {
// ...state[action.index],
// isFlag:!...state[action.index].isFlag,
// }
// ...state.slice(action.index+1)
// ]
function reducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state; // 这一行必不可少 因为初始化时,switch语句就执行的是default 返回初始值
}
}
// 2.创建容器 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(reducer);
// 3.使用状态
class App extends React.Component {
render(){
return (
<div>
获取容器中的值:{store.getState()}
<button onClick={this.add.bind(this)}>增加</button>
<button onClick={this.sub}>减少</button>
{/* 如果函数的调用没有使用当前组件中的数据可以不bind*/}
</div>
)
}
add(){
store.dispatch({type:'INCREMENT'})
// 此时不会立即刷新视图
// 1.如果想刷新可以直接调用forceUpdate
// this.forceUpdate()
// 也可以使用下面的subscribe 监听状态
}
sub(){
store.dispatch({type:'DECREMENT'})
}
}
ReactDOM.render(<App/>.document.getElementById('app'))
// 状态发生变化 就会触发
store.subscribe(() =>
console.log(store.getState())
// 统一更新 this.froceUpdate()
// 或者重新调用ReactDOM.render() render重新渲染就会更新视图
ReactDOM.render(<App/>.document.getElementById('app'))
);
// store.subscribe 会返回一个函数 调用一下这个函数就可以移除监听
// var unSub = store.subscribe(() =>{}); unSub()
规范 设计Action Creator
import { createStore } from 'redux'
function reducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + action.num;
case 'DECREMENT':
return state - action.num;
default:
return state;
}
}
// 抽离action对象 别的组件也可以方便调用 方便灵活传参
// 设计Action Creator
function addCount(num){
return {
type:'INCREMENT',
num
}
}
// 简写
// const addCount = (num)=>{type:'INCREMENT',num}
function subCount(num){
return {
type:'DECREMENT',
num
}
}
let store = createStore(reducer);
// 3.使用状态
class App extends React.Component {
constructor(props){
super(props)
this.state = {
num:1
}
}
render(){
return (
<div>
获取容器中的值:{store.getState()}
<input type="text" value={this.state.num} onChange={(e)=>this.setState({num:+e.target.value})}/>
<button onClick={this.add.bind(this)}>增加</button>
<button onClick={this.sub.bind(this)}>减少</button>
</div>
)
}
add(){
store.dispatch(addCount(this.state.num))
}
sub(){
store.dispatch(subCount(this.state.num))
}
}
ReactDOM.render(<App/>.document.getElementById('app'))
// 状态发生变化 就会触发
store.subscribe(() =>
ReactDOM.render(<App/>.document.getElementById('app'))
);
多个状态 合并reducer
import { createStore,combineReducers } from 'redux'
// 1.定义count的reducer 相当于 vuex 中的{state:{count:0}}
function count(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + action.num;
case 'DECREMENT':
return state - action.num;
default:
return state;
}
}
// 2.定义flag的reducer 相当于 vuex 中的{state:{count:0,flag:false}}
function flag(state = false, action) {
switch (action.type) {
case 'toggle':
return !state
default:
return state;
}
}
// 3.使用combine合并 键值一致可以省写
// 实际传值 --> {'count':count,'flag':flag}
const reducers = combineReducers({
count,flag
})
let store = createStore(reducers);
// 一般在reducer里面设置默认值
// 多个的时候也可以在这里设置默认值,不建议使用
// let store = createStore(reducers,{
// count:0,
// flag:false
// });
class App extends React.Component {
render(){
return (
<div>
test {store.getState().count}
<hr/>
<p> {store.getState().flag} </p>
</div>
)
}
}
ReactDOM.render(<App/>.document.getElementById('app'))
// 状态发生变化 就会触发
store.subscribe(() =>
ReactDOM.render(<App/>.document.getElementById('app'))
);
使用react-redux来优化刷新视图
安装:npm i react-redux -S
是连接React和Redux的桥梁,所以要先引入这两个,在引入react-redux
API
+ Provider: 用于挂载store对象的根组件
+ connect函数:将store中的状态和方法给所有组件去访问。是一个高阶组件的思想,共享数据加强组件。
使用
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore,combineReducers } from 'redux'
import {Provider,Connect} from 'react-redux'
function count(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + action.num;
case 'DECREMENT':
return state - action.num;
default:
return state;
}
}
const reducers = combineReducers({
count
})
let store = createStore(reducers);
// 组件要取store中的数据 或者修改其中的数据 都需要拆分组件
// 拆分为UI组件和容器组件,因为要通过Connect来加强组件 才能共享状态
// UI组件负责视图,而容器组件就是通过Connect加强后的组件
class UIComponent extends React.Component {
render(){
return (
<div>
使用共享的状态:{this.props.count}
<hr/>
使用App传来的数据:{this.props.appData}
<hr/>
<button onClick={this.props.add}>增加</button>
<button onClick={this.props.show}>show</button>
</div>
)
}
}
// 这个函数作用:给UIComponent共享哪些状态
// 将 store中的state 映射为UIComponent中的props
// 如上 在UIComponent中使用props调用
function mapStateToProps(state,parentProps){
// 第一个参数就是state == store.getState
// 第二个参数就是StoreComponent的props对象 用于App传值给UIComponent
return {
count:state.count,
appData:parentProps.appData
}
}
// 这个函数作用:给UIComponent共享哪些操作状态的函数
// 将store中的dispatch映射为UIComponent中的props
function mapDispatchToProps(dispatch,parentProps){
// 第一个参数就是dispatch == store.dispatch
// 第二个参数也一样
return {
add = ()=>dispatch('INCREMENT'),
show:parentProps.show
}
// 调用this.props.add 就会分发dispatch('INCREMENT')
}
// Connect 接受2个参数 都需要是函数,返回一个函数,这个函数去加强UIComponent
const StoreComponent = Connect(mapStateToProps,mapDispatchToProps)(UIComponent)
// StoreComponent就是加强后的容器组件挂载后,共享状态的时候就会自动更新了,
// 而不在需要监听store.subcribe
class App extends React.Component {
render(){
return (
<Provider store={store}>
test {store.getState().count}
<hr/>
<StoreComponent appData="我是app" show={this.show} />
</Provider>
)
}
show = ()=>{
console.log('我是app中的show方法')
}
}
ReactDOM.render(<App/>.document.getElementById('app'))
中间件
如果状态的修改是异步操作,reducer只能做同步的(同mutation),就需要定义一个函数,去完成异步操作然后去dispatch,去触发reducer改变更新数据。
问题在于这个函数怎么才能去调用store.dispatch? vuex里面 actions里面直接能访问store 而react呢
这一个异步操作就需要一个中间件来完成,类似于vuex里面的actions。
vuex:组件 store.dispatch actions里面的函数 => actions里面的异步操作函数 commit mutations里面的函数 => 改变更新数据
redux:组件 store.dispatch => 中间件拦截住 然后完成异步操作之后 在dispatch 触发reducer => 改变更新数据
import { applyMiddleware, createStore } from 'redux'
function count(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state; // 这一行必不可少 因为初始化时,switch语句就执行的是default 返回初始值
}
}
function fn1(){
}
function fn2(){
}
let store = createStore(count,100,applyMiddleware(fn1,fn2));
// 第一个参数:reducer
// 第二个参数可选:reducer的默认值
// 第三个参数可选:一个回调函数,参数是createStore函数本身,用于处理中间件返回的值
class App extends React.Component {
render(){
return (
<div>
获取容器中的值:{store.getState()}
<button onClick={this.add}>增加</button>
<button onClick={this.sub}>减少</button>
</div>
)
}
add(){
store.dispatch({type:'INCREMENT'})
}
sub(){
store.dispatch({type:'DECREMENT'})
}
}
ReactDOM.render(<App/>.document.getElementById('app'))
// 状态发生变化 就会触发
store.subscribe(() =>
ReactDOM.render(<App/>.document.getElementById('app'))
);
理解源码
function fn1(){
}
function fn2(){
}
let store = createStore(count,100,Redux.applyMiddleware(fn1,fn2));
Redux.applyMiddleware(fn1,fn2) 就会返回一个这样的函数 function(createStore){}
这个函数(function(createStore){} )肯定是可以访问到fn1 和 fn2
主要的是 这个函数再什么情况下被调用 fn1 和 fn2 又能做什么?
function(createStore){} 这个函数参数是createStore,就可以在这个函数创建一个初始store,然后经过一些处理,返回新的store容器
function(createStore){
/* 返回一个函数签名跟 createStore 一模一样的函数,亦即返回的是一个增强版的 createStore */
return function(reducer,preloadedState,enhancer){
// 用原 createStore 先生成一个 store,其包含 getState / dispatch / subscribe / replaceReducer 四个 API
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch // 指向原 dispatch
var chain = [] // 存储中间件的数组
// 提供给中间件的 API(其实都是 store 的 API)
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 给中间件“装上” API,即fn1 和 fn2 就可以访问到getState 和 dispatch 了
// fn1 fn2 就是 ...middlewares 去接受的
chain = middlewares.map(middleware => middleware(middlewareAPI))
// chain = [fn1(store),fn2(store)] 这里的store是简易版的
// 串联所有中间件 所以 chain存储的是 fn1 和 fn2 这两个中间件的返回值
// (这两个中间件也一定返回的是一个函数)
// 然后会把dispatch先传给fn2中间调用
// 然后将调用后返回的值 在交给fn1中间件调用
dispatch = compose(...chain)(store.dispatch)
// compose(f, g, h)(action) => f(g(h(action)))
// 例如,chain 为 [M1, M2],而 compose 是从右到左进行“包裹”的
// 最后,我们得到串联后的中间件链:(M1(M2(store.dispatch)))
// 返回一个最新的dispatch函数
// 返回新的容器对象
return {
...store, // store 的 API 中保留 getState / subsribe / replaceReducer
dispatch // 新 dispatch 覆盖原 dispatch,往后调用 dispatch 就会触发 chain 内的中间件链式串联执行
}
}
}
所以真正的fn1 和 fn2 就应该这么来用
function fn2(store){
// 返回一个函数 会在上面compose的时候被调用 且会传进来一个dispatch
return (dispatch)=>{
// dispatch 返回的函数(第三层函数) 才是真正store.dispatch需要调用的函数
// 组件中 store.dipatch(addCount(num))
// addCount(num) == 就是上面说的 Action Creator {type:xxx,payload:xxx} == 也就是这里的action
return (action)=>{
return dispatch(action) // 将{type:xxx,payload:xxx} 最终交给 dispatch去分发
// dispatch(action) 就会触发 fn1 中第三层(最里层函数)函数的调用
}
}
}
function fn1(){
return (dispatch)=>{
return (action)=>{ // 这个函数会被上面fn2最后的dispatch(action)触发
// 如果fn1之前还有中间件 就 return dispatch(action) 没有就直接触发 dispatch(action)
dispatch(action)
// fn1 左边没有中间件了 那么这个dispatch就是最终的的dispatch 就会触发reducer 然后改变更新数据
// 上面那个不会触发reducer 因为它之前(左边)还有中间件
// 这样 我们就可以在这个 dispatch 和 render数据更新 之间做我们想做的事
// 所以可以做一个 logger 如下
}
// return (action)=>{
// console.log(action.type + '触发前',store.getState())
// dispatch(action)
// console.log(action.type + '触发后',store.getState())
// }
}
}
let store = createStore(count,100,Redux.applyMiddleware(fn1,fn2));
异步请求 更新状态
import { applyMiddleware, createStore } from 'redux'
function count(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - action.num;
default:
return state;
}
}
const myThunk = (store) => (dispatch) => (action)=>{
typeof action == 'function' ? action(dispatch) : dispatch(action)
}
let store = createStore(count,100,applyMiddleware(myThunk));
class App extends React.Component {
render(){
return (
<div>
获取容器中的值:{store.getState()}
<button onClick={this.add}>增加</button>
<button onClick={this.sub}>模拟异步减num</button>
</div>
)
}
add(){
store.dispatch({type:'INCREMENT'})
}
sub(){
store.dispatch(this.asyncGetData(1))
}
asyncGetData(num){
return function(dispatch){
setTimeout(()=>{
dispatch({
type:'DECREMENT',
num
})
},100)
}
// 分发一个函数 之前都是分发一个对象
// return的这个函数 被store.dispatch调用了 就会被myThunk的第三层拦截
// 通过验证 这次的action是一个函数 就会把dispatch 传给这个函数action并调用了== action(dispatch)
// 就会执行这个 setTimeout, 这个setTimeout就会在1000毫秒之后,再去dispatch 这个对象action
// 然后又会被myThunk 拦截,这次验证发现是一个对象 ,就会dispatch这个action去触发reducer的更新
// 如果 myThunk之间还有 中间件, 那么 最后验证是一个对象的时候就应该是return dispatch(action) 而不是直接调用了
}
}
ReactDOM.render(<App/>.document.getElementById('app'))
// 状态发生变化 就会触发
store.subscribe(() =>
ReactDOM.render(<App/>.document.getElementById('app'))
);
网友评论