一、结构图
二、代码实现
2.1.redux.js
import React, { useContext, useEffect, useState } from 'react'
// 创建全局上下文
const Context = React.createContext(null)
// 封装上下文组件
export const Provider = ({ store, children }) => {
return <Context.Provider value={store}>{children}</Context.Provider>
}
// 解决多次render,将store和setState提取出来,订阅变化:设置订阅及监听队列
let state = undefined
let reducer = undefined
let listeners = []
const setState = (newState) => {
state = newState
listeners.map((fn) => fn(state))
}
const store = {
// state: { user: { name: 'frank', age: 18 }, group: { name: '前端组' } },
// 将state隐藏,使用getState获得
getState: () => {
return state
},
// 使用dispatch规范setState的流程
dispatch: (action) => {
setState(reducer(state, action))
},
subscribe: (fn) => {
listeners.push(fn)
return () => {
const index = listeners.indexOf(fn)
listeners.splice(index, 1)
}
}
}
// 使redux支持 异步action【函数和Promise】
// 中间件:MiddleWare【可以自己去修饰dispatch】
// 怎么通过中间件让redux支持异步Action:redux-thunk【发现action是个函数就调用它,否则就进入下一个中间件】,redux-promise【发现action.payload是一个Promise,就在Promise后面接上一个then,并将data直接传给payload】
let dispatch = store.dispatch
const preDispatch = dispatch
dispatch = (action) => {
if (action instanceof Function) {
action(dispatch) // 传入dispatch进行递归,既可以处理函数,又可以处理对象,因为这里的action是一个函数,可能会再传一个fn回来
} else {
preDispatch(action) // 可以确定这里的action包含一个type和一个payload
}
}
const preDispatch2 = dispatch
dispatch = (action) => {
if (action.payload instanceof Promise) {
action.payload.then((data) => {
dispatch({ ...action, payload: data }) // 以防再次传入Promise,使用递归
})
} else {
preDispatch2(action)
}
}
// createStore作用:创建store
// state和redux不能是redux内部自定义的,需要外部传入
export const createStore = (_reducer, initState) => {
state = initState
reducer = _reducer
return store
}
// 判断新旧数据是否相等
const changed = (oldData, newData) => {
let changed = false
for (let key in oldData) {
if (oldData[key] !== newData[key]) {
changed = true
}
}
return changed
}
// connect来规范创建中间件:让组件与store全局状态连接起来,由react-redux提供
// (selector,dispatchSelector)=>(Component)=> 表示先接收一个参数,再接收另一个参数
// selector是一个函数,为了实现返回组件需要的state数据,比如只使用user,就返回user:state.user
// dispatchSelector是一个函数,为了实现返回需要的dispatch,比如只修改user,就返回一个userDispatch
// Component是一个组件,实现高阶组件,一个函数接收一个组件,返回一个新的组件
export const connect = (selector, dispatchSelector) => (Component) => {
// React-Redux提供的selector
return (props) => {
const [, update] = useState({})
// 得到具体的data:如果用户传入了selector,就传入局部的state,否则就传入全局的state
const data = selector ? selector(state) : { state }
// 得到具体的dispatch:如果传入了dispatchSelector,就传入局部的dispatchSelector,否则传入全局的dispatch
const dispatchers = dispatchSelector ? dispatchSelector(dispatch) : { dispatch }
// 初次渲染对store进行订阅,如果store值发生变化,在data数据变更的情况下调用update进行相应组件的重新渲染
useEffect(() => {
// store.subscribe 返回一个unsubscribe取消订阅的函数,最好取消订阅,因为不知道selector会不会变化出现重复订阅的情况
return store.subscribe(() => {
// 得到订阅后新的data
const newData = selector ? selector(state) : { state }
// 实现精准渲染,组件只在自己的数据变化时render,判断旧data与新data是否一样,如果不一样才修改,这样如果修改state中的user值用到user的组件会重新渲染,但用到group的组件是不会重新渲染的
if (changed(data, newData)) {
update({})
}
})
}, [selector])
return <Component {...props} {...data} {...dispatchers} />
}
}
2.2.connect.js
import { connect } from './redux'
// 抽取公共selector和dispatch
const userSelector = (state) => {
return { user: state.user } // 只使用了state中的user,redux.js中的data就获取的是user的值
}
const groupSelector = (state) => {
return { group: state.group } // 只使用了state中的group,redux.js中的data就获取的是group的值
}
const userDispatch = (dispatch) => {
return { userDispatch: (attrs) => dispatch({ type: 'updateUser', payload: attrs }) }
}
const groupDispatch = (dispatch) => {
return { groupDispatch: (attrs) => dispatch({ type: 'updateUser', payload: attrs }) }
}
// 将selector与dispatch合并
// connect(MapStateToProps,MapDispatchToProps)(组件),MapStateToProps为了让组件可以快速获取一个局部的state,MapDispatchToProps为了让组件可以快速获取一个局部的dispatch
export const connectToUser = connect(userSelector, userDispatch)
export const connectToGroup = connect(groupSelector, groupDispatch)
2.3.SonScreen.jsx
import React from 'react'
import { Provider, createStore, connect } from './utils/redux'
import { connectToUser, connectToGroup } from './utils/connect'
const initialState = { user: { name: 'frank', age: 18 }, group: { name: '前端组' } }
// 规范创建state的过程,每次更新state时,不能使用原来的state,要创建一个新的state
const reducer = (state, action) => {
const { type, payload } = action
if (type === 'updateUser') {
return {
...state,
user: {
...state.user,
...payload
}
}
} else {
return state
}
}
// 创建store,传入一个reducer【规范state创建流程】,一个initalState【state的初始值】
const store = createStore(reducer, initialState)
export const SonScreen = () => {
return (
<Provider store={store}>
<OneSon />
<TwoSon />
<ThreeSon />
</Provider>
)
}
const OneSon = () => {
return (
<section style={{ color: 'black', border: '1px solid black', marginBottom: '20px' }}>
大儿子7
<User />
</section>
)
}
const TwoSon = () => {
return (
<section style={{ color: 'black', border: '1px solid black', marginBottom: '20px', padding: '20px' }}>
二儿子7
<UserModifier2>内容</UserModifier2>
</section>
)
}
const ThreeSon = connectToGroup(({ group }) => {
return <section style={{ color: 'black', border: '1px solid black' }}>三儿子7 {group.name}</section>
})
const User = connectToUser(({ user }) => {
return <div>User:{user.name}</div>
})
const UserModifier = connectToUser(({ userDispatch, user, children }) => {
const onChange = (e) => {
userDispatch({ name: e.target.value })
}
return (
<div>
{children}
<input value={user.name} onChange={onChange} />
</div>
)
})
// fetch获取public/user.json下面的数据
const fetchFun = async (url) => {
try {
const response = await fetch(process.env.PUBLIC_URL + url)
return response.json() // {name:'康康',age:20}
} catch (error) {
console.error(error)
}
return null
}
const fetchUser = (dispatch) => {
fetchFun('/user.json').then((user) => {
dispatch({ type: 'updateUser', payload: user })
})
}
const UserModifier2 = connect(
null,
null
)(({ state, dispatch }) => {
return (
<div>
<div>User:{state.user.name}</div>
<button
onClick={(e) => {
// 实现异步函数类型的action:为了不写出dispatch(fetchUser)这样的代码
// dispatch(fetchUser)
// 实现异步Promise类型的action,fetchFun('/user.json')返回的是一个Promise
dispatch({ type: 'updateUser', payload: fetchFun('/user.json') })
}}
>
异步获取user
</button>
</div>
)
})
2.4.user.json
如果你使用的是create-react-app 框架创建的,json文件要放在public目录下
{ "name": "康康", "age": 20 }