首先理解
redux
是一个独立的状态管理库,可以用到react 中,也可以用到vue中。react-redux 从名字不难看出,是用来连接react到redux。
react-redux
官网地址
Provider
使组件层级中的 connect() 方法都能够获得 Redux store。
connect
连接 React 组件与 Redux store。
store.js 状态仓库
// store.js
import { createStore } from "redux"
// 实现计数器功能
export function countReducer (state = 1, { type, payload }) {
switch(type) {
case "ADD":
return state + payload
case "MINUS"
return state - payload
default:
return state
}
}
const store = createStore(countReducer)
export default store
index.js 根组件
// index.js
import ReactDom form "react-dom"
import { Provider } from ""react-redux"
import Child from "./Child"
import store from "./store/"
ReactDom.render(
// store就是状态仓库,通过Provider传递
<Provider store={ store }>
<Child />
</Provider>,
document.getElementById("root")
)
Child.js 类组件
// Child.js
import { Component } from "react"
import { connnect } from "react-redux"
import { bindActiveCreators } from "redux"
// connect 接受三个参数
// mapStateToProps 把getState映射到props
// mapDispatchToProps 把dispatch映射到props
// mergeProps 添加参数到props
export function connect(
// mapStateToProps 是一个函数返回是一个state的状态值,可以从新命名
(state) => {
return { count: state.countReducer }
},
// mapDispatchToProps 参数是一个Object或者是一个function
// object
{
add: () => { type: "ADD", payload: 1},
minus: () => {type: "MINUS", payload: 2 }
},
// function 接收一个dispatch
(dispatch) => {
// 两种写法
// 第一种所有的dispatch单独写
const add = () => dispatch({type: "ADD", payload: 1})
const minus = () => dispatch({type: "MINUS", payload: 2)
// 第二种通过redux提供的方法bindActionCreators去合并
let creators = {
add: () => ({type: "ADD", payload: 1}),
minus: () => ({type: "MINUS", payload: 2})
}
const creators = bindActiveCreators(creators, dispatch) // 参数1 creators 参数2 dispatch
return {
...dispatch,
creators
}
},
// mergeProps 用于合并额外参数 函数接收三个参数 stateProps dispatchProps ownProps
// ownProps 需要注意是组件自身的props
(stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
...dispatchProps,
...ownProps,
...{ extra: "haha"}
}
}
)( class Child extends Component {
redner() {
const { count, add, minus } = this.props
return (
<div>{ count }</div>
<button onClick={ add }>+</button>
<button onClick={ minus }>-</button>
)
)
})
函数组件通过hookapi使用
// ChildHookPage.js
import { useSelector, useDispatch, useCallback } from "react-redux"
export function ChildHookPage() {
const count = useSelector(({countReducer}) => countReducer)
const dispatch = useDiaptch()
const add = useCallback(() => {
dispatch(type: "ADD", payload: 1)
}, [])
return (
<div>{ count }</div>
<button onClick={ add }>+</button>
)
}
思路:
第一个方法Provider接收一个store
,children
然后通过context传递store
第二个方法connect是个函数(参数是mapStateToProps
,mapDispatchToProps
,mergeProps
) 返回一个函数(参数是组件自身
),然后又返回一个函数(参数是组件的props
)
Provider.js
// 实现Provider.js
import React from "react"
// 创建context对象
export const Context = React.createContext()
export default function Provider({store, children}) {
// 参数中结构出store和children
// value传递store
return <Context.Provider value={ store }>{ children }</Context.Provider>
}
connect.js
// connect.js
import { useContext, useState, useEffect } from "react"
import { Context } from "./Provider"
import useForceUpdate from './useForceUpdate'
import bindActiveCreators from "./bindActiveCreators"
// 首先connect接受参数mapStateToProps 。。。
export default function connect(mapStateToProps, mapDispatchToProps) {
// 第二个函数参数是组件自身
// 第三个函数参数是组件props
return (WrapComponent) => (props) => {
// 先从Context获取store,因为是函数组件使用useContext
const store = useContext(Context)
// 首先mapStateToProps是一个函数,
// 获取state执行mapStateToProps函数即可(参数是getState)
const stateProps = mapStateToProps(store.getState())
let dispatchProps
// 判断mapDispatchToProps类型
if (typeof mapDispatchToProps === "function") {
// 参数是dispatch
dispatchProps = mapDispatchToProps(store.dispatch)
} else {
// 如果是对象就给每个值上面添加dispatch方法 可以直接调用bindActiveCreators方法
dispatchProps = bindActiveCreators(mapDispatchToProps, store.dispatch)
}
// 自定义hook forceUpdate
const forceUpdate = useForceUpdate()
// forceUpdate写到副作用里面
useLayoutEffect(() => {
store.subscribe(() => {
forceUpdate()
})
return () => {
}
},[store])
// 把state和dispath及自身的props返回
return <WrapComponent {...props} {...stateProps} {...dispatchProps} />
}
}
useForceUpdate.js
// useForceUpdate.js
import { useCallback, useReducer } from 'react'
export default function useForceUpdate() {
// 使用hook中的forceUpdate,但是需要注意每次的值要进行改变,才会去出发forceUpdate,
// test状态其实是没有用到,这里只是用作一个占位,主要是用forceUpdate功能
const [test, forceUpdate] = useReducer(n => n + 1, 2)
const update = useCallback(
() => {
forceUpdate()
},
[],
)
return update
}
// 函数组件更新可以用forceUpdate,在hook中的forceUpdate就是通过setState实现的
// 需要注意,如果前后两个状态值一样就不回去进行更新,所以用useReducer中每次更新的状态值不一样。
useSelector.js
// useSelector
import { Context } from "./Provider"
import { useContext } from "react"
import forceUpdate from "./useForceUpdate"
export default useSelector(selector) {
const store = useContext(Context)
const forceUpdate = useForceUpdate()
// 这里写法同connect一致
// 写到副作用函数中进行一个订阅
useLayoutEffect(() => {
const unSubscribe = store.subscribe(() => {
forceUpdate()
})
return () => {
unSubscribe()
}
}, [store])
return selector(store.getState())
}
useDispatch.js
// useDispatch
import { Context } from "./Provider"
import { useContext } from "react"
export default useDispatch() {
// 直接返回dispatch即可
return useContext(Context).dispatch
}
上面connect中的mapDispatchToProps
中用到了bindActiveCreators
方法,此方法实现也非常简单,思路:就是对所有的对象上添加dispatch方法
即可,
// bindActiveCreators.js
export default function bindActiveCreators(creators, dispatch) {]
// 两个参数,1,所有dispatch的方法对象,2,dispatch
const obj = {}
Object.keys(creators).forEach( n => {
// key不变,返回一个函数添加上dispatch方法
// 参数就是creators遍历出来函数的执行结果
obj[n] = () => dispatch(creators[n]())
})
return obj
}
以上代码未进行测试,完整代码: https://gitee.com/uijwuy/react-ex
如果对redux还不熟悉的同学,可以看这篇redux的实现
react-redux
: 相当于是react连接redux的中间件,使操作store更方便简洁
(connect
),不需要手动组件更新的操作。了解了原理,可以自己手动封装使用。
函数组件更新
: 可以用forceUpdate,但是需要主要前后两个状态值不能一致
父组件更新子组件是否更新
的依据是props和引用的context的value是否改变
useEffect
和useLayoutEffect
区别:调用机制不同,一个异步一个同步。useEffect组建渲染之后执行,useLayoutEffect是dom变更之后渲染之前调用。