前奏
使用react-redux
为什么要使用?
每次都重新调用render跟getState不是很高级, 想使用更react的方式来写,需要react-redux的支持.
安装
yarn add react-redux
提供了两个api
- Provider为后代组件提供store
- connect为组件提供数据和变更方法
API
使组件层级中的connect()方法都能够获得Redux store. 正常情况下,你的根组件应该嵌套在<Provider>
中才能使用connect()的方法.
连接react组件与Redux store. 返回一个新的已与Redux store连接的组件类.
使用
全局提供store, index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
//import {Provider} from './reactKredux';
import store from './store';
ReactDOM.render(
<Provider store={store}><App/></Provider>,
// <App/>,
document.getElementById('root')
);
获取状态数据, ReactReduxPage.js
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
// 函数组件中使用react-redux,connect
const ReactReduxPage = (props) => {
let {count, add, minus} = props;
return (
<div>
<h3>ReactReduxPage</h3>
<p>{count}</p>
<button onClick={() => {
add()
}}>
加
</button>
<button onClick={() => {
minus()
}}>
减
</button>
</div>
)
}
export default connect(
//mapStateToProps把state map(映射)props上一份
({count}) => ({count}),
//mapDispatchToProps object|function
(dispatch) => {
let result = {
add: () => ({type: 'ADD'}),
minus: () => ({type: 'MINUS'})
}
result = bindActionCreators(result, dispatch);
return {dispatch, ...result}
}
)(ReactReduxPage)
bindActionCreators是个函数, 这里作用是: 给action绑定dispatch方法, 相当于dispatch({type: ‘ADD’}), 否则没有触发dispatch因此数据就不会更新. connect相当于一个高阶组件, 传入一个组件,经过传入参数加工, 返回一个有store的新组件.
具体细节请移步 : redux文档
效果如下:
核心
手写实现react-redux
创建reactKredux.js的文件, 去实现:
- [ bindActionCreators ]
- [ Provider ]
- [ connect ]
bindActionCreators
先看一下bindActionCreators的实现,reactKredux.js
const bindActionCreator = (action, dispatch) => {
return (...args) => (dispatch(action(...args)))
}
const bindActionCreators = (actions, dispatch) => {
let bindActions = {};
for (let key in actions) {
bindActions[key] = bindActionCreator(actions[key], dispatch);
}
console.log(bindActions);
return bindActions
}
代码很短, 实现也很简单, 就是定义一个函数,里面有两个参数:actions,dispatch ,返回一个内部属性返回值被dispatch包裹的对象,如下:
Provider
Provider的实现,reactKredux.js
import {useContext, createContext, useEffect, useReducer,useState,useCallback} from 'react'
//react跨层级传递数据 context;
// 1.创建Context;
let Context = createContext();
//2.Provider 传递value
const Provider = ({store, children}) => {
return <Context.Provider value={store}> {children}</Context.Provider>
}
里面需要用到Context: react跨层级传递数据
使用Context一共三个步骤:
- 创建Context, createContext()
- Provider 传递value, <Context.Provider value={}>
- 子组件消费Context value
消费方式有3种:- contextType(只能用在类组件中,只能订阅单一的context的来源)
- useContext(只能用在函数组件以及自定义hook中)
- Consumer (没有限制, 两种组件中都可以使用)
使用Provider,传递数据store, index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// import {Provider} from 'react-redux';
import {Provider} from './reactKredux';
import store from './store';
ReactDOM.render(
<Provider store={store}><App/></Provider>,
// <App/>,
document.getElementById('root')
);
App页面, App.js
import './App.css';
import ReactRudexPage from './pages/ReactReduxPage';
import ReactRudxHookPage from './pages/ReactReduxHookPage';
// import store from './store';
function App() {
return (
<div className="App">
{/*<ReactRudxHookPage/>*/}
<ReactRudexPage/>
</div>
);
}
export default App;
Connect
Connect的实现,reactKredux.js
const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => (props) => {
//因为是函数组件, 所以使用useContext拿到Context的value值,store.
let store = useContext(Context);
//store里有getState获取数据,dispatch触发Action,subscribe订阅
const {getState, dispatch, subscribe} = store;
let mapStates, mapdispatch = {dispatch};
mapStates = mapStateToProps(getState());
if (typeof mapDispatchToProps === 'object') {
mapdispatch = bindActionCreators(mapDispatchToProps, dispatch);
} else {
mapdispatch = mapDispatchToProps(dispatch);
}
// const [count, forceUpdate] = useReducer(x => x + 1, 0);
//强制更新
const forceUpdate=useForceUpdata();
//订阅state的变更
useEffect(() => {
const unSubscribe = subscribe(() => forceUpdate());
//组件在卸载前取消订阅
return () => {
unSubscribe()
}
}, [store])
return <NewComponent {...props} {...mapStates} {...mapdispatch} />
}
const useForceUpdata=()=>{
const [state,setState]=useState(0);
const forceUpdate=useCallback(()=>{setState((pre)=>pre+1)},[]);
return forceUpdate;
}
-
connect 是个函数, 外部接受参数:mapStateToProps,mapDispatchToProps,内部接受参数:WrappedComponent(当前的使用store的组件名),WrappedComponent接受参数:props(组件自己的props).最终返回一个经过加工的新组件.
-
mapStateToProps 是个函数, 接受一个Store里面的所有状态值, 用store.getState拿到.最后返回想要的state值,
({count}) => ({count})
因此直接调用mapStateToProps(getState())
就可以了. -
mapDispatchToProps 可以是个函数也可以是个对象, 所以先进行类型判断, 如果是个对象, 就使用
bindActionCreators
返回一个经过dispatch加工的新对象. 如果是个函数, 就直接调用函数, 传入参数dispatch
-
订阅 如果没有这一步, 则界面无法更新状态值; store里的subscribe接受一个函数,函数里返回一个强制更新函数的调用.
subscribe(() => forceUpdate())
, 每当dispatch时, 也就是state改变时, store就触发forceUpdate()
,这时候组件就会更新. -
取消订阅 最后, 组件卸载前需要取消订阅, 也就是触发
unSubscribe()
, 这个其实就是store的subscribe返回操作, 里面会删除对应的订阅.
index.js和ReactReduxPage.js里面对connect,bindActionCreators,Provider的引入, 换成我们自己写的,如下:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// import {Provider} from 'react-redux';
import {Provider} from './reactKredux';
import store from './store';
ReactDOM.render(
<Provider store={store}><App/></Provider>,
// <App/>,
document.getElementById('root')
);
ReactReduxPage.js
// import {connect} from 'react-redux';
// import {bindActionCreators} from 'redux';
import {bindActionCreators,connect} from '../reactKredux'
// 函数组件中使用react-redux,connect
const ReactReduxPage = (props) => {
let {count, add, minus} = props;
return (
<div>
<h3>ReactReduxPage</h3>
<p>{count}</p>
<button onClick={() => {
add()
}}>
加
</button>
<button onClick={() => {
minus()
}}>
减
</button>
</div>
)
}
export default connect(
//mapStateToProps把state map(映射)props上一份
({count}) => ({count}),
//mapDispatchToProps object|function
(dispatch) => {
let result = {
add: () => ({type: 'ADD'}),
minus: () => ({type: 'MINUS'})
}
result = bindActionCreators(result, dispatch);
return {dispatch, ...result}
}
)(ReactReduxPage)
这是store的文件, store/index.js
import {createStore, combineReducers} from 'redux';
export const countReducer = (state = 0, action) => {
switch (action.type) {
case 'ADD':
return state + 1;
case 'MINUS':
return state-1;
default:
return state;
}
}
//
const store = createStore(combineReducers({count:countReducer}));
// const store = createStore(countReducer);
export default store;
看下最终效果:
最终效果一样
react-redux Hooks API及手写实现
使用
两个API:
useSelector 获取store state
useDispatch 获取dispatch
ReactReduxHookPage.js
import {useCallback} from "react";
import {useDispatch, useSelector} from "react-redux";
// import {useDispatch, useSelector} from "../reactKredux";
export default({value})=>{
const dispatch=useDispatch();
const count=useSelector(({count})=>count);
console.log(count);
return (
<div>
<h3>ReactReduxHookPage</h3>
<p>{count}</p>
<button onClick={useCallback(()=>{dispatch({type:'ADD'})},[])}>
加
</button>
<button onClick={useCallback(()=>{dispatch({type:'MINUS'})},[])}>
减
</button>
</div>
)
}
因为上面组件的react-redux引用的是官方的, 因此index.js里面的react的redux也要换成官方的"react-redux",不然会报错,因为不是同一个store以及找不到对应的Provider, App.js里面的页面引用ReactReduxHookPage 组件. 如下:
ReactReduxHookPage.js
import './App.css';
import ReactRudexPage from './pages/ReactReduxPage';
import ReactRudxHookPage from './pages/ReactReduxHookPage';
// import store from './store';
function App() {
return (
<div className="App">
<ReactRudxHookPage/>
{/* <ReactRudexPage/>*/}
</div>
);
}
export default App;
最后下过如下:
手写实现
reactKredux.js
//hooks
//get
const useSelector = (select) => {
const store = useStore();
const getState=store.getState();
//需要订阅(否则不会更新视图)
// const [count,forceUpdate]=useReducer((x)=>x+1,0);
const forceUpdate=useForceUpdata();
useEffect(()=>{
const unSubScribe=store.subscribe(()=>forceUpdate());
return ()=>{
unSubScribe();
}
},[store])
return select(getState);
}
//set
const useDispatch = () => {
const store = useStore();
return store.dispatch;
}
//获取store
const useStore = () => {
//返回store
const store = useContext(Context);
return store;
}
const useForceUpdata=()=>{
const [state,setState]=useState(0);
const forceUpdate=useCallback(()=>{setState((pre)=>pre+1)},[]);
return forceUpdate;
}
里面的useSelector和useDispatch分别相当于,get和set.
useSelector: 接受一个函数参数, 而这个函数里面又接受getState参数, 返回想要的state值. getState通过store获取,如:store.getState,store又通过useContent获取. 因此里面做的事情就是, 直接调用这个函数参数select, 并传入参数getState. 最后返回. 并且里面要做订阅和取消订阅.
useDispatch: 就是拿到store里面的dispatch , 很简单, store.dispatch获取并返回就可以了.
reactKredux完整代码:
reactKredux.js
import {useContext, createContext, useEffect, useReducer,useState,useCallback} from 'react'
//react跨层级传递数据 context;
// 1.创建Context;
let Context = createContext();
//2.Provider 传递value
const Provider = ({store, children}) => {
return <Context.Provider value={store}> {children}</Context.Provider>
}
// 3.消费Context value
// 三种消费方式:
// 1)只能用在类组件 :contextType ,只能订阅单一的context
// 2) 只能用在函数组件 useContext
// 3)类组件函数组件皆可 Consumer
const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => (props) => {
//因为是函数组件, 所以使用useContext拿到Context的value值,store.
let store = useContext(Context);
//store里有getState获取数据,dispatch触发Action,subscribe订阅
const {getState, dispatch, subscribe} = store;
let mapStates, mapdispatch = {dispatch};
mapStates = mapStateToProps(getState());
if (typeof mapDispatchToProps === 'object') {
mapdispatch = bindActionCreators(mapDispatchToProps, dispatch);
} else {
mapdispatch = mapDispatchToProps(dispatch);
}
// const [count, forceUpdate] = useReducer(x => x + 1, 0);
//强制更新
const forceUpdate=useForceUpdata();
//订阅state的变更
useEffect(() => {
const unSubscribe = subscribe(() => forceUpdate());
//组件在卸载前取消订阅
return () => {
unSubscribe()
}
}, [store])
return <NewComponent {...props} {...mapStates} {...mapdispatch} />
}
const bindActionCreator = (action, dispatch) => {
return (...args) => (dispatch(action(...args)))
}
const bindActionCreators = (actions, dispatch) => {
let bindActions = {};
for (let key in actions) {
bindActions[key] = bindActionCreator(actions[key], dispatch);
}
console.log(bindActions);
return bindActions
}
//hooks
//get
const useSelector = (select) => {
const store = useStore();
const getState=store.getState();
//需要订阅(否则不会更新视图)
// const [count,forceUpdate]=useReducer((x)=>x+1,0);
const forceUpdate=useForceUpdata();
useEffect(()=>{
const unSubScribe=store.subscribe(()=>forceUpdate());
return ()=>{
unSubScribe();
}
},[store])
return select(getState);
}
//set
const useDispatch = () => {
const store = useStore();
return store.dispatch;
}
//获取store
const useStore = () => {
//返回store
const store = useContext(Context);
return store;
}
const useForceUpdata=()=>{
const [state,setState]=useState(0);
const forceUpdate=useCallback(()=>{setState((pre)=>pre+1)},[]);
return forceUpdate;
}
export {bindActionCreators, connect, Provider,useSelector,useDispatch};
完结
(ps:第一次写技术博客,最近在学习源码,记录一番. 自己加深下印象以及锻炼下表达能力. 里面可能有许多不足与疏忽之处,望各位大佬多多指导以及指出)