Redux
纯原生基本使用
redux源码的本质是发布订阅模式。subscribe方法接受函数(订阅函数),当dispatch(action)的时,内部会将上一次的state和本次的action传给reducer函数进行执行,将reducer返回的函数更新为新的state,然后再取出之前subscribe方法订阅好的函数进行执行。
仓库对象提供的核心方法:
- getState
- subscribe
- dispatch
html
xml
<div>
<p id="counter">0</p>
<button id="add-button">+</button>
<button id="minus-button">-</button>
</div>
js
ini
import { createStore } from 'redux';
const counter = document.getElementById('counter');
const addButton = document.getElementById('add-button');
const minusButton = document.getElementById('minus-button');
// 动作类型
const ADD = 'ADD';
const MINUS = 'MINUS';
//初始状态数据
const initState = { number: 0 };
function reducer(state = initState, action) {
switch (action.type) {
case ADD:
return { number: state.number + 1 };
case MINUS:
return { number: state.number - 1 };
default:
return state;
}
}
// 创建仓库
const store = createStore(reducer);
function render() {
counter.innerText = store.getState().number;
}
render();
store.subscribe(()=>{
render()
});
addButton.addEventListener('click', function () {
store.dispatch({ type: ADD });
});
minusButton.addEventListener('click', function () {
store.dispatch({ type: MINUS });
});
核心实现
scss
function createStore(reducer){
let state;
const listeners = [];
function getState(){
return state;
}
function subscribe(listener){
listeners.push(listener)
return ()=>{
const listenerIndex = listeners.indexOf(listener)
listeners.splice(listenerIndex,1)
}
}
function dispatch(action){
state = reducer(state,action)
listeners.forEach((l)=>{ l() })
}
dispatch({type:"@@redux/init"})
return {
getState,
subscribe,
dispatch
}
}
export default createStore
结合react使用
javascript
// 动作类型
const ADD = 'ADD';
const MINUS = 'MINUS';
//初始状态数据
const initState = { number: 0 };
function reducer(state = initState, action) {
switch (action.type) {
case ADD:
return { number: state.number + 1 };
case MINUS:
return { number: state.number - 1 };
default:
return state;
}
}
const store = createStore(reducer);
class Counter extends React.Component{
constructor(props){
super(props)
this.state = {number:store.getState().number}
}
componentDidMount(){
this.unsubscribe = store.subscribe(()=>{
this.setState({number:store.getState().number})
})
}
componentWillUnmount(){
this.unsubscribe()
}
render(){
<div>
<p>{this.state.number}</p>
<button onClick={()=>store.dispatch({type:ADD})}>+</button>
<button onClick={()=>store.dispatch({type:MINUS})}>-</button>
</div>
}
}
优化
- 将action的创建交给函数调用返回
- 将action生成函数传递一个函数bindActionCreators,该函数中将action生成器函数包装为store.dispatch(actionCreater)}
csharp
function add (){
return {type:ADD}
}
function minus (){
return {type:MINUS}
}
const actionCreators = {add,minus}
const boundActionCreators = bindActionCreators(actionCreators, store.dispatch)
<button onClick={boundActionCreators.add}>+</button>
redux工具方法实现
scss
function bindActionCreators(actionCreators,dispatch){
const boundActionCreators ={}
for(const key in actionCreators){
boundActionCreators[key] = function(...args){
dispatch(actionCreators[key](...args))
}
}
return boundActionCreators
}
export default bindActionCreators
-
合并多个reducer
当UI派发一个动作action之后,redux源码中不知道这个动作具体会命中那个reducer中的type,所以会全部循环一边所有的reducer并将state和action传递过去,所以如果多个reducer中都有同一个type类型,都会被触发修改(这是redux库这么设计的)。
javascriptfunction combineReducers(reducers){ // 这个combination函数就会被传递给下面的createStore return function combination(state={},action){ let nextState ={} for(let key in reducers){ let prevStateForKey = state[key] let reducerForKey = reducers[key] prevStateForKey = reducerForKey(nextStateForKey,action) nextState[key] = prevStateForKey // 看似没有state[key] = nextState[key]这行代码,但是实际更新后的state被存放在了外层函数中了 } return nextState } } export default combineReducers function createStore(reducer){ let state; const listeners = []; function getState(){ return state; } function subscribe(listener){ listeners.push(listener) return ()=>{ const listenerIndex = listeners.indexOf(listener) listeners.splice(listenerIndex,1) } } function dispatch(action){ state = reducer(state,action) listeners.forEach((l)=>{ l() }) } dispatch({type:"@@redux/init"}) return { getState, subscribe, dispatch } } export default createStore
javascript
import React from 'react';
import { bindActionCreators } from 'redux';
import store from '@/store';
import actionCreators from '@/store/actionCreators/counter1';
const boundActionCreators = bindActionCreators(actionCreators, store.dispatch);
//boundActionCreators={add:()=>dispatch({ type: ADD }),minus}
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { number: store.getState().counter1.number };
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => this.setState({
number: store.getState().counter1.number
}));
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={boundActionCreators.add1}>+</button>
<button onClick={boundActionCreators.minus1}>-</button>
<button onClick={() => store.dispatch({ type: 'DOUBLE' })}>DOUBLE</button>
</div >
)
}
}
export default Counter;
上面的只使用redux 的代码还是存在冗余:
- 每个组件都需自己去订阅触发组件重新渲染的方发和当组件卸载时注销组件重新渲染的方法
- 组件需要引入仓库并将仓库中的状态数据映射为组件的state中
React-Redux
基本使用
创建仓库
javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import {Provider} from 'react-redux'
import store from './store'
import Counter from './components/client'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<Counter>
</Provider>
)
类组件中使用
scala
import React from 'react';
import actionCreators from '../store/actionCreators/counter1';
import { connect } from 'react-redux';
class Counter1 extends React.Component {
render() {
return (
<div>
<p>{this.props.number}</p>
<button onClick={this.props.add1}>+</button>
<button onClick={this.props.minus1}>-</button>
</div >
)
}
}
//把仓库中的状态映射为组件的属性props对象 仓库到组件的输出
const mapStateToProps = state => state.counter1;
//const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch)
export default connect(
mapStateToProps,
actionCreators //组件的输出,在组件里派发动作,修改仓库 ,这个参数可以是对象也可以是函数,函数则会接受store.dispatch作为参数
)(Counter1);
函数组件中使用
javascript
import React from 'react';
import actionCreators from '../store/actionCreators/counter2';
import { useSelector, useDispatch, useBoundDispatch } from 'react-redux';
function Counter2() {
//1.从状态树中获取某一部分状态,进行渲染 2.当仓库中的状态发生改变后会重新渲染组件
const counter2 = useSelector(state => state.counter2);
//const dispatch = useDispatch();//store.dispatch
const { add2, minus2 } = useBoundDispatch(actionCreators); // useBoundDispatch原生库中并没有实现,自己实现的
return (
<div>
<p>{counter2.number}</p>
<!-- <button onClick={()=>dispatch(actionCreators.add2())}>+</button> -->
<button onClick={add2}>+</button>
<button onClick={minus2}>-</button>
</div >
)
}
export default Counter2;
基本实现
react-redux内部是借助了react原生提供的createContext API实现的,所以需要先了解createContext 的基本使用才行。
Provider组件实现
javascript
import React from 'react'
import ReactReduxContext from './ReactReduxContext'
export default function Provider(props){
return (
<ReactReduxContext.Provider value={{store:props.store}}>
{props.children}
</ReactReduxContext.Provider>
)
}
ReactReduxContext
javascript
import React from 'react'
const ReactReduxContext = React.createContext(null)
export default ReactReduxContext
connect
connect方法本质是一个高阶组件:
javascript
import React from 'react'
import ReactReduxContext from './ReactReduxContext'
import {bindActionCreators} from 'redux'
function connect(mapStateToProps,mapDispatchToProps){
return function(Component){
return class extends React.Component{
static contextType = ReactReduxContext
constructor(props,context){
super(props)
const {store} = context
const {getState, dispatch, subscribe} = store
this.state = mapStateToProps(getState())
this.unsubscribe = subscribe(()=>{
this.setState(mapStateToProps(getState()))
})
let dispatchProps
if(typeof mapDispatchToProps === 'function'){
dispatchProps = mapDispatchToProps(dispatch)
}else{
dispatchProps = bindActionCreators(mapDispatchToProps,dispatch)
}
this.dispatchProps = dispatchProps
}
componentWillUnmount(){
this.unsubscribe()
}
render(){
return (
<Component {...this.props} {...this.state} {...this.dispatchProps}></Component>
)
}
}
}
}
useSelector
scss
import { useContext, useState, useLayoutEffect, useReducer, useRef } from 'react';
import ReactReduxContext from '../ReactReduxContext'
function useSelector(selector){
const { store } = useContext(ReactReduxContext);
const lastSelectedState = useRef(null)
// const [state,setState] = useState(0)
const [,forceUpdate] = useReducer(x=>x+1,0)
useLayoutEffect(()=>{
store.subscribe(()=>{
// 优化,避免不必要的更新
let slectedState = selector(store.getState())
if(lastSelectedState.current === slectedState){
return
}
lastSelectedState.current = slectedState
// setState(state+1)
forceUpdate()
})
},[])
return selector(store.getState())
}
export default useSelector
useDispatch
javascript
import ReactReduxContext from '../ReactReduxContext'
function useDispatch(){
const { store } = useContext(ReactReduxContext);
return store.dispatch
}
export default useDispatch
useBoundDispatch
工具方法,react-redux中并没有,自己实现的。
javascript
import { useContext } from 'react';
import {bindActionCreators} from 'redux'
function useBoundDispatch(actionCreators){
const { store } = useContext(ReactReduxContext);
return bindActionCreators(actionCreators, store.dispatch)
}
export default useBoundDispatch