0.代码
-
react-redux: GitHub - nefe/redux-in-chinese: Redux 中文文档
-
我的Github仓库上的完整代码以及提交:YuyuanW的手写redux代码
一、reducer的前身:state的传递修改问题
使用上下文完成state的传递,进行读写
import React, {useState, useContext} from 'react'
//0.创建上下文
const appContext = React.createContext(null)
const App = () => {
const [appState, setAppState] = useState({
user: {name: 'yuyuan', age: 18}
})
//赋值
const contextValue = {appState, setAppState}
return (
<appContext.Provider value={contextValue}>
<大儿子/>
<二儿子/>
<幺儿子/>
</appContext.Provider>
)
}
const 大儿子 = () => <section>大儿子<User/></section>
const 二儿子 = () => <section>二儿子<UserModifier/></section>
const 幺儿子 = () => <section>幺儿子</section>
const User = () => {
//使用上下文进行读取数据
const contextValue = useContext(appContext)
return <div>User:{contextValue.appState.user.name}</div>
}
const UserModifier = () => {
//使用上下文进行读写数据
const {appState,setAppState} = useContext(appContext)
const onChange = (e) => {
//上下文写数据
setAppState({...appState,user:{...appState.user,name:e.target.value}})
}
return <div>
<input placeholder ={appState.user.name}
onChange={onChange}/>
</div>
}
export default App
二、reducer:规范state创建流程
- 将修改state数据的过程抽象为函数
const User = () => {
const contextValue = useContext(appContext)
return <div>User:{contextValue.appState.user.name}</div>
}
const newState = (state,type,action)=>{
if(type==='updateState'){
return {
...state,user:{
...state.user,...action
}
}
}
else{
return state
}
}
const UserModifier = () => {
const {appState,setAppState} = useContext(appContext)
const onChange = (e) => {
setAppState(newState(appState,'updateState',{name:e.target.value}))
}
return <div>
<input placeholder ={appState.user.name}
onChange={onChange}/>
</div>
}
- 得到reducer的函数形式
import React, {useState, useContext} from 'react'
const appContext = React.createContext(null)
const App = () => {
const [appState, setAppState] = useState({
user: {name: 'yuyuan', age: 18}
})
const contextValue = {appState, setAppState}
return (
<appContext.Provider value={contextValue}>
<大儿子/>
<二儿子/>
<幺儿子/>
</appContext.Provider>
)
}
const 大儿子 = () => <section>大儿子<User/></section>
const 二儿子 = () => <section>二儿子<UserModifier/></section>
const 幺儿子 = () => <section>幺儿子</section>
const User = () => {
const contextValue = useContext(appContext)
return <div>User:{contextValue.appState.user.name}</div>
}
//更改函数形式
const reducer = (state,{type,payload})=>{
if(type==='updateState'){
return {
...state,user:{
...state.user,...payload
}
}
}
else{
return state
}
}
const UserModifier = () => {
const {appState,setAppState} = useContext(appContext)
const onChange = (e) => {
//更改使用形式
setAppState(reducer(appState,{type:'updateState',payload:{name:e.target.value}}))
}
return <div>
<input placeholder ={appState.user.name}
onChange={onChange}/>
</div>
}
export default App
三、dispatch:规范setState的流程
props的使用
为什么需要props,不能单独写一个setState的函数?
- 因为setState需要原始的数据,就需要useContext,所以需要新建一个组件,去传递props,并且需要改变组件的使用方式。
const 二儿子 = () => <section>二儿子<UserModifier/></section>
// ==>
const 二儿子 = () => <section>二儿子<Wrapper/></section>
// new Component
const Wrapper = ()=> <UserModifier dispatch={dispatch} state={appState}/>
//props传递
const UserModifier = ({dispatch,state})=>{}
dispatch的创建和使用
const Wrapper = ()=>{
const {appState,setAppState} = React.useContext(appContext)
const dispatch = (action)=>{
setAppState(reducer(appState,action))
}
return <UserModifier dispatch={dispatch} state={appState}></UserModifier>
}
完整代码
import React, {useState, useContext} from 'react'
const appContext = React.createContext(null)
const App = () => {
const [appState, setAppState] = useState({
user: {name: 'yuyuan', age: 18}
})
const contextValue = {appState, setAppState}
return (
<appContext.Provider value={contextValue}>
<大儿子/>
<二儿子/>
<幺儿子/>
</appContext.Provider>
)
}
const 大儿子 = () => <section>大儿子<User/></section>
const 二儿子 = () => <section>二儿子<Wrapper/></section>
const 幺儿子 = () => <section>幺儿子</section>
const User = () => {
const contextValue = useContext(appContext)
return <div>User:{contextValue.appState.user.name}</div>
}
const reducer = (state,{type,payload})=>{
if(type==='updateState'){
return {
...state,user:{
...state.user,...payload
}
}
}
else{
return state
}
}
const Wrapper = ()=>{
const {appState,setAppState} = React.useContext(appContext)
const dispatch = (action)=>{
setAppState(reducer(appState,action))
}
return <UserModifier dispatch={dispatch} state={appState}></UserModifier>
}
const UserModifier = ({dispatch,state}) => {
const onChange = (e)=>{
dispatch({type:'updateState',payload:{name:e.target.value}})
}
return <div>
<input placeholder={state.user.name} onChange={onChange}></input>
</div>
}
export default App
四、connect
封装Wrapper,使其由函数创建
import React, {useState, useContext} from 'react'
const appContext = React.createContext(null)
const App = () => {
const [appState, setAppState] = useState({
user: {name: 'yuyuan', age: 18}
})
const contextValue = {appState, setAppState}
return (
<appContext.Provider value={contextValue}>
<大儿子/>
<二儿子/>
<幺儿子/>
</appContext.Provider>
)
}
const 大儿子 = () => <section>大儿子<User/></section>
const 二儿子 = () => <section>二儿子<UserModifier /></section>
const 幺儿子 = () => <section>幺儿子</section>
const User = () => {
const contextValue = useContext(appContext)
return <div>User:{contextValue.appState.user.name}</div>
}
const reducer = (state,{type,payload})=>{
if(type==='updateState'){
return {
...state,user:{
...state.user,...payload
}
}
}
else{
return state
}
}
//创建Wrapper
const connect = (Component)=>{
//传递自由命名组件中的props
const Wrapper = (props)=>{
const {appState,setAppState} = React.useContext(appContext)
const dispatch = (action)=>{
setAppState(reducer(appState,action))
}
return <Component {...props} dispatch={dispatch} state={appState} />
}
return Wrapper
}
//需要传入的组件
const _UserModifier = ({dispatch,state,children}) => {
const onChange = (e)=>{
dispatch({type:'updateState',payload:{name:e.target.value}})
}
return <div>
<input placeholder={state.user.name} onChange={onChange}></input>
</div>
}
//自由命名组件
const UserModifier = connect(_UserModifier)
export default App
五、redux的出现
问题
前面的代码使用起来没什么大问题,但是有一个问题,就是渲染的次数太多了,每次修改数据,都会让所有的组件都重新渲染。组件数量一多,就非常消耗内存。
思路
- 改数据后渲染是useState的执行触发的,所以更改数据的函数
const store = {
appState : {
user: {name: 'yuyuan', age: 18}
},
setAppState : (newState)=>{
store.appState = newState
}
}
const App = () => {
return (
<appContext.Provider value={store}>
<大儿子/>
<二儿子/>
<幺儿子/>
</appContext.Provider>
)
}
- 让使用appState数据的组件重新读取数据,重新渲染
- 触发渲染
- 定位到需要渲染的组件
// 通过useState触发渲染
connect = (Component)=>{
const Wrapper = (props)=>{
const {appState,setAppState} = React.useContext(appContext)
const [,update] = useState({})
const dispatch = (action)=>{
update({})
setAppState(reducer(appState,action))
}
return <Component {...props} dispatch={dispatch} state={appState} />
}
return Wrapper
}
//通过一开始的渲染,将需要定位组件写入数组
const store = {
appState : {
user: {name: 'yuyuan', age: 18}
},
setAppState : (newState)=>{
store.appState = newState
// 数据更新,定位到了组件中的数据也会精准更新
store.listener.map(fn=>fn(store.appState))
},
// listener
listener : [] ,
// 写入方法
addListener(fn){
store.listener.push(fn)
return ()=>{
const index= store.listener.indexOf(fn)
store.listener.splice(index,1)
}
}
}
const connect = (Component)=>{
const Wrapper = (props)=>{
const {appState,setAppState} = React.useContext(appContext)
const [,update] = useState({})
// 页面初次渲染时,完成listener的写入;
//update触发Wrapper的重新渲染;
//Wrapper重新渲染,实现setAppState中新数据的写入
useEffect(()=>{
store.addListener(()=>{update({})})
},[])
const dispatch = (action)=>{
setAppState(reducer(appState,action))
}
return <Component {...props} dispatch={dispatch} state={appState} />
}
return Wrapper
}
- 最后完整代码
//App.jsx
import React from 'react'
import {appContext,connect,store} from './redux'
const App = () => {
return (
<appContext.Provider value={store}>
<大儿子/>
<二儿子/>
<幺儿子/>
</appContext.Provider>
)
}
const 大儿子 = () => {
console.log('大儿子执行了',Math.random());
return <section>大儿子<User/></section>}
const 二儿子 = () => {
console.log('二儿子执行了',Math.random());
return <section>二儿子<UserModifier /></section>}
const 幺儿子 = () => {
console.log('幺儿子执行了',Math.random());
return <section>幺儿子</section>}
const _User = () => {
return <div>User:{store.appState.user.name}</div>
}
const _UserModifier = ({dispatch,state,children}) => {
const onChange = (e)=>{
dispatch({type:'updateState',payload:{name:e.target.value}})
}
return <div>
<span>{children}</span>
<input placeholder={state.user.name} onChange={onChange}></input>
</div>
}
const UserModifier = connect(_UserModifier)
const User = connect(_User)
export default App
//redux.jsx
import React, {useState, useContext} from 'react'
import { useEffect } from 'react'
export const appContext = React.createContext(null)
export const store = {
appState : {
user: {name: 'yuyuan', age: 18}
},
setAppState : (newState)=>{
store.appState = newState
store.listener.map(fn=>fn(store.appState))
},
listener : [] ,
addListener(fn){
store.listener.push(fn)
return ()=>{
const index= store.listener.indexOf(fn)
store.listener.splice(index,1)
}
}
}
const reducer = (state,{type,payload})=>{
if(type==='updateState'){
return {
...state,user:{
...state.user,...payload
}
}
}
else{
return state
}
}
export const connect = (Component)=>{
const Wrapper = (props)=>{
const {appState,setAppState} = React.useContext(appContext)
const [,update] = useState({})
useEffect(()=>{
store.addListener(()=>{update({})})
},[])
const dispatch = (action)=>{
setAppState(reducer(appState,action))
}
return <Component {...props} dispatch={dispatch} state={appState} />
}
return Wrapper
}
六、react-redux库中的selector
- 提供对数据使用时的简化 例如:使用user替代appState.user
- selector的作用就是传递简化数据使用的方法
// App.jsx中
const User = connect(appState=>{return {user:appState.user}})(({user}) => {
return <div>User:{user.name}</div>
})
const UserModifier = connect(appState=>{return {user:appState.user}})(({children,user,dispatch}) => {
const onChange = (e)=>{
dispatch({type:'updateState',payload:{name:e.target.value}})
}
return <div>
<span>{children}</span>
<input placeholder={user.name} onChange={onChange}></input>
</div>
})
- 所以selector的API应该怎么写呢?
//redux.jsx中
export const connect = (selector) => (Component)=>{
const Wrapper = (props)=>{
const {appState,setAppState} = useContext(appContext)
const [,update] = useState({})
// 更改传出的数据
const data = selector ? selector(appState) : {appState}
useEffect(()=>{
store.addListener(()=>{update({})})
},[])
const dispatch = (action)=>{
setAppState(reducer(appState,action))
}
// 传递给组件的发生变化
return <Component {...props} {...data} dispatch={dispatch} />
}
return Wrapper
}
- 使用selector的时候,实现精准渲染
七、mapToDispatchProps
和上面的selector一样,当我在使用的时候不想写那么长,那么dispatch就要进行优化了。
- API的使用
// App.jsx中
const UserModifier = connect(appState=>{return {user:appState.user}},
//将updateUser代替dispatch的使用
dispatch=>{return {updateUser:(attrs)=>dispatch({type:'updateState',payload:attrs})}
})(({children,user,updateUser}) => {
const onChange = (e)=>{
//使用替代
updateUser({name:e.target.value})
}
return <div>
<span>{children}</span>
<input placeholder={user.name} onChange={onChange}></input>
</div>
})
- API的声明
// redux.jsx中
export const connect = (selector,mapDispatchToProps) => (Component)=>{
const Wrapper = (props)=>{
const {appState,setAppState} = useContext(appContext)
const [,update] = useState({})
const dispatch = (action)=>{
setAppState(reducer(appState,action))
}
const data = selector ? selector(appState) : {appState}
const dispatchers = mapDispatchToProps ? mapDispatchToProps(dispatch) : dispatch
useEffect(()=>{
store.addListener(()=>{update({})})
},[])
return <Component {...props} {...data} {...dispatchers} />
}
return Wrapper
}
- 将这种方法连贯一下就是
-
我需要在使用原先方法的时候,简化用法,也就是封装给别名
- 于是我在使用的时候写好要传入的封装方法,对象中写好别名
-
在Api的定义中,传入封装方法,更改数据,然后传出
-
八、关于connect的意义
-
封装读写的接口,暴露给用户使用
-
将上面的代码优化一下能得到:
//App.jsx中
const userState = appState=>{return {user:appState.user}}
const userDispatch = dispatch=>{return {updateUser:(attrs)=>dispatch({type:'updateState',payload:attrs})}}
//最后暴露出来的接口:
const userConnect = connect(userState,userDispatch)
const UserModifier = userConnect(({children,user,updateUser}) => {
const onChange = (e)=>{
updateUser({name:e.target.value})
}
return <div>
<span>{children}</span>
<input placeholder={user.name} onChange={onChange}></input>
</div>
})
const User = userConnect(({user}) => {
return <div>User:{user.name}</div>
})
- 然后单独提取为一个文件
九、封装Provider和createStore
1.createStore()
createStore需要解决什么问题?
- 在我们的模型中,appState和reducer,原始数据和修改数据的方法是写死的,所以我们需要暴露接口给用户进行编写
- 接口长什么样子呢?
createStore(reducer,initState),由用户去定义reducer函数和initState对象 - 接口如何编写?
//reducer.jsx中
import React, {useState, useContext} from 'react'
import { useEffect } from 'react'
export const appContext = React.createContext(null)
export const store = {
//reducer和state都放入store中,且初始值为未定义
reducer:undefined,
appState : {},
setAppState : (newState)=>{
store.appState = newState
store.listener.map(fn=>fn(store.appState))
},
listener : [] ,
addListener(fn){
store.listener.push(fn)
return ()=>{
const index= store.listener.indexOf(fn)
store.listener.splice(index,1)
}
}
}
//暴露接口createStore,返回新的store
export const createStore = (reducer,initState)=>{
store.reducer = reducer
store.appState = initState
return store
}
export const connect = (selector,mapDispatchToProps) => (Component)=>{
const Wrapper = (props)=>{
const {appState,setAppState} = useContext(appContext)
const [,update] = useState({})
const dispatch = (action)=>{
//将用到reducer的地方改为store.reducer
setAppState(store.reducer(appState,action))
}
const data = selector ? selector(appState) : {appState}
const dispatchers = mapDispatchToProps ? mapDispatchToProps(dispatch) : dispatch
useEffect(()=>{
store.addListener(()=>{update({})})
},[])
return <Component {...props} {...data} {...dispatchers} />
}
return Wrapper
}
//App.jsx
import {appContext,connect,createStore,store} from './redux'
const reducer = (state,{type,payload})=>{
if(type==='updateState'){
return {
...state,user:{
...state.user,...payload
}
}
}
else{
return state
}
}
const userState = {
user: {name: 'yuyuan', age: 18},
group : {name:'hunan'}
}
createStore(reducer,userState)
2.Provider
Provider干什么用的?怎么用的?
-
在我们的App.jsx中,我们需要引入上下文然后给用户进行创建上下文组件,为什么我们不直接提供带有上下文的组件?这样用户直接使用就好了,就不用自己定义了。
-
简而言之就是我们需要封装一下Provider
//redux.jsx
export const Provider = ({store,children})=>{
return <appContext.Provider value={store}>{children}</appContext.Provider>
}
//App.jsx
import {connect,createStore,store,Provider} from './redux'
const App = () => {
return (
<Provider store={store}>
<大儿子/>
<二儿子/>
<幺儿子/>
</Provider>
)
}
十、对Redux的概念进行一个总结
写了这么多代码究竟是完成一个什么样的东西?
- 简言之就是,我提供对数据的处理方法的一个库,这个库里面有一些很好用的API
- App中基本就是对接口的使用,redux就是编写接口
connect干了什么?
export const connect = (selector,mapDispatchToProps) => (Component)=>{
const Wrapper = (props)=>{
// 获取原始数据
const {appState,setAppState} = useContext(appContext)
const [,update] = useState({})
// 优化数据的更新
const dispatch = (action)=>{
setAppState(store.reducer(appState,action))
}
// 对原始数据的使用进行了简单的封装
const data = selector ? selector(appState) : {appState}
const dispatchers = mapDispatchToProps ? mapDispatchToProps(dispatch) : dispatch
//触发更新
useEffect(()=>{
store.addListener(()=>{update({})})
},[])
//导出
return <Component {...props} {...data} {...dispatchers} />
}
return Wrapper
}
- connect是暴露这些接口最直接的一个组件,所有的读写方法都在里面,所以封装为了一个组件,以便传出这些接口
- 首先,connect获取了原始数据
- 然后,connect对原始数据的用户的使用需求进行了简单处理
- 其次,connect为了更改原始的数据,订阅后update了一下
- 最后,导出组件和接口
最后就是将state和reducer的自定义接口暴露给用户:createState
十一、封装一下redux.jsx保持和官方文档一致
- 简单处理一下store中的数据,变为redux.jsx中的全局变量,store中只暴露出用户需要的API
import React, {useState, useContext} from 'react'
import { useEffect } from 'react'
export const appContext = React.createContext(null)
let appState = undefined
let reducer = undefined
let listener = []
const setAppState = (newState)=>{
appState = newState
listener.map(fn=>fn(appState))
}
export const store = {
getState : ()=>{
return appState
},
dispatch : (action)=>{
setAppState(reducer(appState,action))
},
addListener(fn){
listener.push(fn)
return ()=>{
const index= listener.indexOf(fn)
listener.splice(index,1)
}
}
}
export const createStore = (_reducer,initState)=>{
reducer = _reducer
appState = initState
return store
}
export const connect = (selector,mapDispatchToProps) => (Component)=>{
const Wrapper = (props)=>{
// const {appState,setAppState} = useContext(appContext)
const [,update] = useState({})
const data = selector ? selector(appState) : {appState}
const dispatchers = mapDispatchToProps ? mapDispatchToProps(store.dispatch) : store.dispatch
useEffect(()=>{
store.addListener(()=>{update({})})
},[])
return <Component {...props} {...data} {...dispatchers} />
}
return Wrapper
}
export const Provider = ({store,children})=>{
return <appContext.Provider value={store}>{children}</appContext.Provider>
}
waiting 未完待续
十二、Redux支持函数action
目前的Redux不支持异步的action
解决一下这个问题