状态(State)
state不过就是一个变量,一个用来记录(组件)状态的变量。组件可以根据不同的状态值切换为不同的显示,比如,用户登录和没登录看到页面应该是不同的,那么用户的登录与否就应该是一个状态。再比如,数据加载与否,显示的界面也应该不同,那么数据本身就是一个状态。
使用
使用Redux之前,你需要先明确一点Redux是JS应用的状态容器,它并不是只能在React使用,而是可以应用到任意的JS应用中(包括前端JS,和服务器中Node.js)。总之,凡是JS中需要管理的状态的Redux都可以胜任。
React中使用Redux
引入react-redux库
我们除了需要引入Redux核心库外,还需要引入react-redux库,以使React和redux适配,可以通过npm或yarn安装:
npm install -S redux react-redux
yarn add redux react-redux
创建reducer:
Redux是一个状态容器,所以使用Redux必须先创建容器对象,它的所有操作都是通过容器对象来进行的
const reducer = (state = {
name: '孙悟空',
age: 18,
gender: '男',
address: '花果山'
}, action) => {
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.payload
};
case 'SET_AGE':
return {
...state,
age: action.payload
};
case 'SET_ADDRESS':
return {
...state,
address: action.payload
};
case 'SET_GENDER':
return {
...state,
gender: action.payload
};
default :
return state
}
};
创建store
createStore用来创建一个Redux中的容器对象,它需要三个参数:reducer、preloadedState、enhancer。
1、reducer是一个函数,是state操作的整合函数,每次修改state时都会触发该函数,它的返回值会成为新的state。
2、preloadedState就是state的初始值,可以在这里指定也可以在reducer中指定。
3、enhancer增强函数用来对state的功能进行扩展
Redux.createStore(reducer, [preloadedState], [enhancer])
const store = createStore(reducer);
创建store后,需要引入react-redux中提供的Provider组件,将其设置到所有组件的最外层,并且将刚刚创建的store设置为组件的store属性,只有这样才能使得Redux中的数据能被所有的组件访问到。
设置provider:
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App/>
</Provider>
);
钩子函数useSelector
钩子函数useSelector,用于获取Redux中存储的数据,它需要一个回调函数作为参数,回调函数的第一个参数就是当前的state,回调函数的返回值,会作为useSelector的返回值返回,所以state => state
表示直接将整个state作为返回值返回。
const stu = useSelector(state => state);
<p>
{stu.name} -- {stu.age} -- {stu.gender} -- {stu.address}
</p>
useDispatch派发器-操作数据
useDispatch同样是react-redux提供的钩子函数,用来获取redux的派发器,对state的所有操作都需要通过派发器来进行。
通过派发器修改state:
dispatch({type:'SET_NAME', payload:'猪八戒'})
dispatch({type:'SET_AGE', payload:28})
dispatch({type:'SET_GENDER', payload:'女'})
dispatch({type:'SET_ADDRESS', payload:'高老庄'})
完整举例
import ReactDOM from 'react-dom/client';
import {Provider, useDispatch, useSelector} from "react-redux";
import {createStore} from "redux";
const reducer = (state = {
name: '孙悟空',
age: 18,
gender: '男',
address: '花果山'
}, action) => {
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.payload
};
case 'SET_AGE':
return {
...state,
age: action.payload
};
case 'SET_ADDRESS':
return {
...state,
address: action.payload
};
case 'SET_GENDER':
return {
...state,
gender: action.payload
};
default :
return state
}
};
const store = createStore(reducer);
const App = () =>{
const stu = useSelector(state => state);
const dispatch = useDispatch();
return <div>
<p>
{stu.name} -- {stu.age} -- {stu.gender} -- {stu.address}
</p>
<div>
<button onClick={()=>{dispatch({type:'SET_NAME', payload:'猪八戒'})}}>改name</button>
<button onClick={()=>{dispatch({type:'SET_AGE', payload:28})}}>改age</button>
<button onClick={()=>{dispatch({type:'SET_GENDER', payload:'女'})}}>改gender</button>
<button onClick={()=>{dispatch({type:'SET_ADDRESS', payload:'高老庄'})}}>改address</button>
</div>
</div>
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<div>
<Provider store={store}>
<App/>
</Provider>
</div>
);
复杂案例State
reducer:
const reducer = (state = {
stu: {
name: '孙悟空',
age: 18,
gender: '男',
address: '花果山'
},
school: {
name: '花果山一小',
address: '花果山大街1号'
}
}, action) => {
switch (action.type) {
case 'SET_NAME':
return {
...state,
stu: {
...state.stu,
name: action.payload
}
};
case 'SET_AGE':
return {
...state,
stu: {
...state.stu,
age: action.payload
}
};
case 'SET_ADDRESS':
return {
...state,
stu: {
...state.stu,
address: action.payload
}
};
case 'SET_GENDER':
return {
...state,
stu: {
...state.stu,
gender: action.payload
}
};
case 'SET_SCHOOL_NAME':
return {
...state,
school: {
...state.school,
name:action.payload
}
};
case 'SET_SCHOOL_ADDRESS':
return {
...state,
school: {
...state.school,
address: action.payload
}
}
default :
return state;
}
};
数据层次变多了,我们在操作数据时也变得复杂了,比如:
case 'SET_NAME':
return {
...state,
stu: {
...state.stu,
name: action.payload
}
};
同时数据加载的逻辑也要修改,之前我们是将整个state返回,现在我们需要根据不同情况获取state,比如获取学生信息要这么写:
//获取学生信息
const stu = useSelector(state => state.stu);
//获取学校信息:
const school = useSelector(state => state.school);
多个Reducer
Redux中是允许我们创建多个reducer的,所以上例中的reducer我们可以根据它的数据和功能进行拆分,拆分为两个reducer,像是这样:
const stuReducer = (state = {
name: '孙悟空',
age: 18,
gender: '男',
address: '花果山'
}, action) => {
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.payload
};
case 'SET_AGE':
return {
...state,
age: action.payload
};
case 'SET_ADDRESS':
return {
...state,
address: action.payload
};
case 'SET_GENDER':
return {
...state,
gender: action.payload
};
default :
return state;
}
};
const schoolReducer = (state = {
name: '花果山一小',
address: '花果山大街1号'
}, action) => {
switch (action.type) {
case 'SET_SCHOOL_NAME':
return {
...state,
name: action.payload
};
case 'SET_SCHOOL_ADDRESS':
return {
...state,
address: action.payload
};
default :
return state;
}
};
修改后reducer被拆分为了stuReducer和schoolReducer,拆分后在编写每个reducer时,只需要考虑当前的state数据,不再需要对无关的数据进行复制等操作,简化了reducer的编写。于此同时将不同的功能编写到了不同的reducer中,降低了代码间的耦合,方便对代码进行维护。
合并:
拆分后,还需要使用Redux为我们提供的函数combineReducer将多个reducer进行合并,合并后才能传递进createStore来创建store。
const reducer = combineReducers({
stu:stuReducer,
school:schoolReducer
});
const store = createStore(reducer);
combineReducer需要一个对象作为参数,对象的属性名可以根据需要指定,比如我们有两种数据stu和school,属性名就命名为stu和school,stu指向stuReducer,school指向schoolReducer。读取数据时,直接通过state.stu读取学生数据,通过state.school读取学校数据。
完整案例:
import ReactDOM from 'react-dom/client';
import {Provider, useDispatch, useSelector} from "react-redux";
import {combineReducers, createStore} from "redux";
const stuReducer = (state = {
name: '孙悟空',
age: 18,
gender: '男',
address: '花果山'
}, action) => {
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.payload
};
case 'SET_AGE':
return {
...state,
age: action.payload
};
case 'SET_ADDRESS':
return {
...state,
address: action.payload
};
case 'SET_GENDER':
return {
...state,
gender: action.payload
};
default :
return state;
}
};
const schoolReducer = (state = {
name: '花果山一小',
address: '花果山大街1号'
}, action) => {
switch (action.type) {
case 'SET_SCHOOL_NAME':
return {
...state,
name: action.payload
};
case 'SET_SCHOOL_ADDRESS':
return {
...state,
address: action.payload
};
default :
return state;
}
};
const reducer = combineReducers({
stu:stuReducer,
school:schoolReducer
});
const store = createStore(reducer);
const App = () => {
const stu = useSelector(state => state.stu);
const school = useSelector(state => state.school);
const dispatch = useDispatch();
return <div>
<p>
{stu.name} -- {stu.age} -- {stu.gender} -- {stu.address}
</p>
<div>
<button onClick={() => {
dispatch({type: 'SET_NAME', payload: '猪八戒'});
}}>改name
</button>
<button onClick={() => {
dispatch({type: 'SET_AGE', payload: 28});
}}>改age
</button>
<button onClick={() => {
dispatch({type: 'SET_GENDER', payload: '女'});
}}>改gender
</button>
<button onClick={() => {
dispatch({type: 'SET_ADDRESS', payload: '高老庄'});
}}>改address
</button>
</div>
<hr/>
<p>
{school.name} -- {school.address}
</p>
<div>
<button onClick={() => {
dispatch({type: 'SET_SCHOOL_NAME', payload: '高老庄小学'});
}}>改学校name
</button>
<button onClick={() => {
dispatch({type: 'SET_SCHOOL_ADDRESS', payload: '高老庄中心大街15号'});
}}>改学校address
</button>
</div>
</div>;
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<div>
<Provider store={store}>
<App/>
</Provider>
</div>
);