React 官方并没有提供对应的状态机插件,因此,我们需要下载第三方的状态机插件 —— Redux。
在终端中定位到项目根目录,然后执行以下命令下载 Redux
npm i redux
在 React 中,不会自动生成状态机的相关配置代码,因此,需要我们自己手动去创建目录以及配置文件。
我们可以在 src
的目录中创建一个 redux
或 store
的目录,用来存放所有关于状态机的配置文件。然后,在该目录中创建一个 store.js
文件,作为整个状态机的入口文件。
import {legacy_createStore as createStore} from 'redux'
const store=createStore();
createStore
方法接收一个函数作为参数,该函数的返回值,会保存到 store 仓库中
const store = createStore((state = 数据初始值) => {
return state;
});
通过函数参数的默认值的方式,来设置仓库数据的初始值
在 store
仓库对象身上,提供了一个 getState()
方法,用来查看仓库中的所有数据:
console.log(store.getState());
由于目前没有任何文件中在引入状态机的配置文件,如果我们需要查看仓库中的数据,暂时需要在 index.js
中引入 /redux/store.js
文件来让其运行。
state:状态,就是我们传递的数据
action是一个通知对象,里面必须有一个type属性,表示当前通知的类型,至于其他属性,你可以任意添加
可以通过store.dispatch(action对象)来更新仓库中的数据
注意:
在实际开发中,更多人喜欢用action创建函数
在实际开发中,大多数情况下,type会被定义成字符串常量
reducer本质是一个函数,它用来响应发送过来的actions,经过处理把state发送给store
在reducer函数中,需要return返回值,这样store才能接收到数据
reducer函数接收两个参数,第一个参数是初始化store,第二个参数是action
数据仓库,存放组件数据的地方。一个项目一般只有一个数据仓库,store可以把action和reducer联系在一起。
主要的职责
维护应用的state
提供getState()方法获取state
提供dispatch()方法发送action
通过subscribe()来注册监听
通过subscribe()返回值来注销监听
在src目录下创建一个actions文件夹
在该目录下创建一个index.js文件
action是一个通知对象,里面必须有一个type属性,这里的num属性是自定义的表示计数器每次增加的个数
const incrementAction={
type:'increment',
num:1
}
export {incrementAction}
在src目录下创建reducers目录
在该目录下创建index.js文件,用来构建reducer,注意reducer要接收两个参数
第一个参数是默认状态,我们可以定义一个初始化的state,然后进行赋值
在函数里面判断第二个参数action的type值是否是我们发送过的,如果是,我们可以通过return返回新的state
const counterReducer=(state=0,action)=>{
switch(action.type){
case 'increment':
return state+action.num
default:
return state
}
}
export {counterReducer}
在src目录下创建一个文件夹store
在该目录下创建index.js文件,用来构建store,注意createStore函数第一个参数接收的是reducer
import {legacy_createStore as createStore} from 'redux'
import {counterReducer} from '../reducers'
const store=createStore(counterReducer)
export default store
创建components文件夹,该目录下存放自定义组件
在该目录下新建Counter.jsx文件
将Counter.jsx引入到App.js文件中
import Counter from "./components/Counter";
function App() {
return (
);
}
export default App;
调用dispatch函数,更新仓库的数据
import React,{useEffect,useState} from 'react'
import store from '../redux/store'
import {incrementAction} from '../redux/actions'
export default function Counter() {
const increment=()=>{
store.dispatch(incrementAction)
}
return (
计数器:{store.getState()}
)
}
当组件加载完毕后,调用 store. subscribe注册监听,监听state数据的变化
import React,{useEffect,useState} from 'react'
import store from '../redux/store'
import {incrementAction} from '../redux/actions'
export default function Counter() {
const [count, setCount] = useState(0);
const increment=()=>{
store.dispatch(incrementAction)
}
useEffect(()=>{
store.subscribe(()=>{
console.log('正在监控'+store.getState());
setCount({})
})
})
return (
计数器:{store.getState()}
)
}
我们一般会在页面里面修改数据,即,在页面里面调用store的dispatch方法。那就意味着action对象中的num字段很大可能是动态的,即不同的页面num字段可能是不同的,这样我们就不能把action写成一个死的对象,最好是封装成一个函数,执行过后返回一个action通知对象,然后num的值通过函数的参数来决定。这样的函数我们称之为action创建函数(action creator)
const incrementAction=(num)=>{
return{
type:'increment',
num:num
}
}
export {incrementAction}
在实际开发中,type在多数情况下会被定义成常量,如下所示
在src/actions目录下,新建actionTypes.jsx文件,将所有type类型定义成常量
export const INCREMENT ="increment"
在action creator中引入
import {INCREMENT} from './constant'
const incrementAction=(num)=>{
return{
type:INCREMENT,
num:num
}
}
export {incrementAction}
项目中肯定不止一个数据,所以state的默认值应该是一个对象,而不是其他。而且除了默认值,每当case到一个条件,返回一个新的对象时,应该返回一个全新的对象,然后才是你要修改的数据(当然这些数据,如果是引用类型的话,应该修改其引用本身,否则界面可能不会更新,尽管数据发生变化了
import { INCREMENT } from '../actions/constant'
const counterReducer = (state = { num: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return {
...state,
num: state.num + action.num
}
default:
return state
}
}
export { counterReducer }
优化后,在组件中调用的代码如下
import React,{useEffect,useState} from 'react'
import store from '../redux/store'
import {incrementAction} from '../redux/actions'
export default function Counter() {
const [count, setCount] = useState(0);
const increment=()=>{
store.dispatch(incrementAction(2))
}
useEffect(()=>{
store.subscribe(()=>{
console.log('正在监控'+store.getState());
setCount({})
})
})
return (
计数器:{store.getState()}
)
}
为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,本文主要介绍它。
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件有以下几个特征。
只负责 UI 的呈现,不带有任何业务逻辑
没有状态(即不使用this.state
这个变量)
所有数据都由参数(this.props
)提供
不使用任何 Redux 的 API
容器组件有以下几个特征。
负责管理数据和业务逻辑,不负责 UI 的呈现
带有内部状态
使用 Redux 的 API
总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
npm i react-redux
npm i redux
在constant.jsx中添加INCREMENT
export const INCREMENT="increment";
在src/actions/index.jsx中添加incrementAction
import { INCREMENT } from "./constant";
export const incrementAction=num=>({type:INCREMENT,num})
在src/reducers/index.jsx中添加counterReducer
import {INCREMENT} from '../actions/constant'
const counterReducer=(state={num:0},action)=>{
switch(action.type){
case INCREMENT:
return{
...state,
num:state.num+action.num
}
default:
return state
}
}
export {counterReducer}
在src/store/index.jsx中编写代码如下
import {legacy_createStore as createStore} from 'redux'
import {counterReducer} from '../reducers'
const store=createStore(counterReducer)
export default store
在index.js中导入Provider组件
import {Provider} from 'react-redux'
利用provider组件将整个接口进行包裹
给Provider组件设置store的属性,该属性的值就是通过createStore构建出来的store实例对象
import ReactDOM from 'react-dom/client'
import App from './App';
import {Provider} from 'react-redux'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
Provider
在根组件外面包了一层,这样一来,App
的所有子组件就默认都可以拿到state
了。
由于UI组件不能使用Redux的API所以,如果在组件中如果要使用Redux就必须将UI组件变成容器类组件
React-Redux 提供connect
方法,用于从 UI 组件生成容器组件。connect
的意思,就是将这两种组件连起来
import React from 'react'
import { connect } from 'react-redux'
function Counter() {
render() {
return (
)
}
}
export default connect()(Counter)
connect
方法接收一个回调函数作为参数:
const mapStateToProps = () => {
}
export default connect(mapStateToProps)(Counter);
该回调函数本身,又可以通过第一个参数接收到仓库中的所有数据
const mapStateToProps = (state) => {
console.log(state); // 仓库中所有的数据
}
export default connect(mapStateToProps)(Counter);
在该回调函数中,返回一个对象,该对象会和组件的 props 进行合并。换句话说,该函数的返回值会添加到组件的 props 中:
const mapStateToProps = (state) => {
return {
数据名: 从 state 中获取的数据值
}
}
export default connect(mapStateToProps)(Counter);
处理完成后,我们就可以在组件的 props 中访问到对应的仓库数据了:
function Counter(props)
render() {
return (
计数器
{props.数据名}
)
}
}
修改仓库数据,依然是通过 dispatch()
方法来触发修改操作。
在组件中,只要和仓库关联过,就能在 props 上直接获取到 dispatch 方法。因此,在组件中可以直接通过 props.dispatch()
方法来触发修改数据的操作。
import React,{useEffect,useState} from 'react'
import store from '../redux/store'
import {incrementAction} from '../redux/actions'
import { connect } from 'react-redux';
function Counter(props) {
const increment=()=>{
props.dispatch(incrementAction(3))
}
return (
计数器:{props.num}
)
}
const mapStateToProps = (state) => {
console.log(state); // 仓库中所有的数据
return{
num:state.num
}
}
export default connect(mapStateToProps)(Counter)
针对 React 中的函数组件,React-Redux 中也提供了第三方的 Hook。
通过调用 useSelector
方法,并传递一个回调函数作为参数,我们可以在这个回调函数中获取到仓库中的 state。
import { useSelector } from 'react-redux'
useSelector((state) => {
console.log(state); // 仓库中所有的数据
})
然后我们可以在回调函数中,将我们需要使用的数据 return
出来,然后用一个变量来接收:
const data = useSelector((state) => {
return 数据;
})
后续组件要使用数据,就可以直接通过变量进行数据的访问了。
调用 useDispatch()
方法,可以直接获取到 dispatch()
方法。
import { useDispatch } from 'react-redux'
export default fucntion Test() {
const dispatch = useDispatch();
return (
)
}
如果组件中使用 Hook 来获取 dispatch
方法的话,就不再需要使用 connect
来对组件和仓库进行关联了。
获取到 dispatch
方法后,后续的使用就和 props.dispatch
的使用一致。
关键代码
import React from 'react'
import {useDispatch,useSelector} from 'react-redux'
import {incrementAction} from '../../redux/action'
export default function Counter(props) {
const num=useSelector((state)=>{
return state.num
})
const dispatch=useDispatch()
const increment=()=>{
dispatch(incrementAction(3))
}
return (
计数器:{num}
)
}
当项目越来越大的时候,需要管理的数据也会越来越多,如果所有的数据都由一个reducer管理的话,则这个reducer肯定会变得非常的臃肿,且难以维护。所以有必要对reducer做一个拆分,不同功能模块的数据切片,由不同的reducer来管理。假设现在有两个模块,账户管理模块和商品管理模块,每个模块都有数据需要管理
import {combineReducers} from 'redux'
import counterReducer from './counterReducer'
export default combineReducers({
counter:counterReducer
})
注意:调用时候需要使用使用到切片的名字才能访问到,比如
import React from 'react'
import {useSelector} from 'react-redux'
export default function Counter(props) {
const count=useSelector((state)=>{
console.log(state);
return state.counter.num //state.切片名的key.num
})
return (
计数器:{count}
)
}