1、创建react项目
1、使用 React 的脚手架工具来搭建项目
javascript
npx create-react-app my-app
cd my-app
npm start
2、JSX基础语法
1、基础语法
在 React 中,使用 JSX 来描述页面。
js
function App() {
return (
<div>Hello React~</div>
);
}
你可以把类似于 HTML 的代码单独提取出来,例如:
js
function App() {
const ele = <div>Hello React~</div>
return (
ele
);
}
JSX的本质其实就是 React.createElement 方法的一种语法糖。
javascript
const element2 = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
注意这种类似于 HTML 的语法在 React 中称之为 JSX , 这是一种 JavaScript 的语法扩展。在 React 中推荐使用 JSX 来描述用户界面。JSX 乍看起来可能比较像是模版语言,但事实上它完全是在 JavaScript 内部实现的。
使用 JSX 来描述页面时,有如下的一些语法规则:
- 根元素只能有一个
- JSX 中使用 JavaScript 表达式。表达式写在花括号 {} 中
- 属性值指定为字符串字面量,或者在属性值中插入一个 JavaScript 表达式
- style 对应样式对象,class 要写作 className
- 注释需要写在花括号
- JSX 允许在模板中插入数组,数组会自动展开所有成员
2、组件与事件绑定
在 React 中无法通过 return false 来阻止默认行为,所以只有使用 e.preventDefault 的方式来阻止默认行为。
javascript
function Form() {
function handleSubmit(e) {
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
如果是类组件,那么事件处理函数写作一个类方法。// 现在不怎么用类组件了
javascript
class Welcome extends React.Component {
// 事件处理函数
eventHandler(e){
window.alert('Hello');
e.preventDefault();
}
render() {
return (
<a href="https://www.baidu.com/" onClick={this.eventHandler}>this is a test</a>
);
}
}
3、组件状态
组件状态
早期类组件被称之为有状态组件,就是因为在类组件中能够维护组件数据。
js
class 类名 extends React.Component{
constructor(){
super();
// 设置组件自身的数据状态
this.state = {
xxx : xxx
}
}
render(){
return (
// 通过 {this.state.xxx} 来获取状态数据
)
}
}
// 或者
class 类名 extends React.Component{
state = {
xxx : xxx
}
render(){
return (
// 通过 {this.state.xxx} 来获取状态数据
)
}
}
不要直接去修改状态值,而是应该通过 setState 方法修改组件的 state 状态数据。
js
this.setState({
xxx: 新值
})
setState ,它对状态的改变,可能是异步的。
如果改变状态的代码处于某个 HTML 元素的事件中,则其是异步的,否则是同步
如果在事件处理函数里面想拿到 setState 执行后的数据,可以提前使用一个变量来存储计算结果,或者使用 setState 的第二个参数,它是一个函数,这个函数会在 state 更新后被调用。
React 会对异步的 setState 进行优化,将多次 setState 进行合并(将多次状态改变完成后,再统一对 state 进行改变,然后触发 render)
4、props传参
和 Vue 一样,在 React 中组件会存在层级关系,那么自然会涉及到组件之间进行数据的传递。
如果是父组件向子组件传递数据,则使用 props。
如果是函数组件,props 作为函数的一个参数传入:
js
function 组件名(props) {
return (
// 一段 JSX
// 通过 props.xxx 获取传入的值
<div>
<p>姓名:{props.name}</p>
<p>年龄:{props.age}</p>
<p>性别:{props.gender}</p>
</div>
);
}
如果是类组件,则需要在 constructor 中将 props 通过 super 传递给父类,然后通过 this.props 的方式来获取传入的值:
js
class 组件名 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
// 一段 JSX
// 通过 this.props.xxx 获取传入的值
<div>
<p>姓名:{this.props.name}</p>
<p>年龄:{this.props.age}</p>
<p>性别:{this.props.gender}</p>
</div>
);
}
}
通过 props.children ,可以实现类似于 Vue 中插槽的功能,例如:
js
class 组件B extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<div>
{this.props.children}
</div>
);
}
}
class 组件A extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<组件B>
<p>Hello, React</p>
<p>Hello, Redux</p>
<p>Hello, Facebook</p>
<p>Hello, Google</p>
</组件B>
);
}
}
通过 defaultprops 就可以对 props 设置默认值。
js
function Greeting(props) {
const { name, age, gender } = props;
return (
<div>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>性别:{gender}</p>
</div>
);
}
// 设置默认的 props 值,当组件没有传值时会使用默认值
Greeting.defaultProps = {
name : 'xiejie',
age : 18,
gender : 'male'
};
js
class Greeting extends React.Component {
constructor(props) {
super(props);
}
// 设置默认的 defaultProps 属性值
static defaultProps = {
name : "xiejie",
age : 18,
gender : 'male'
}
render() {
return (
<div>
<p>姓名:{this.props.name}</p>
<p>姓名:{this.props.age}</p>
<p>姓名:{this.props.gender}</p>
</div>
);
}
}
// 或者
Greeting.defaultProps = {
name : "xiejie",
age : 18,
gender : 'male'
}
props 的类型检查
javascript
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
javascript
import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
export default HelloWorldComponent
5、状态提升
父传子通过 props ,子传父通过触发自定义事件。
在 React 中,如果子组件需要向父组件传递数据,同样是通过触发父组件传递给子组件的事件来进行传递,被称之为"状态提升"。
父组件
javascript
import React from "react";
import Money from "./component/Money";
// 类组件
class App extends React.Component {
state = {
dollar: "",
rmb: ""
}
transformToRMB = (value) => {}
transformToDollar = (value) => {}
render() {
return (
<div>
<Money text="美元" money={this.state.dollar} transform={this.transformToRMB} />
<Money text="人民币" money={this.state.rmb} transform={this.transformToDollar} />
</div>
)
}
}
export default App;
子组件
js
import React from 'react';
function Money(props) {
function handleChange(e){
// 在子组件中,要做的事情很简单,将用户数据的值,传递给父组件
// 让父组件来进行修改
props.transform(e.target.value);
}
return (
<fieldset>
<legend>{props.text}</legend>
<input type="text" value={props.money} onChange={handleChange}/>
</fieldset>
);
}
export default Money;
6、受控和非受控组件
受控组件
受控组件,本质上其实就是将表单中的控件和视图模型(状态)进行绑定,之后都是针对状态进行操作。
javascript
import React from "react";
// 类组件
class App extends React.Component {
state = {
value : ""
}
handleChange = (e) => {
this.setState({
value : e.target.value
})
}
clickHandle = () => {
// 提交整个表单
console.log(`你要提交的内容为:${this.state.value}`);
}
render() {
return (
<div>
<input type="text" value={this.state.value} onChange={this.handleChange}/>
<button onClick={this.clickHandle}>提交</button>
</div>
)
}
}
export default App;
非受控组件
在某些特殊情况下,需要使用以前传统的 DOM 方案进行处理,此时替代的方案就是非受控组件。
javascript
import React from "react";
// 类组件
class App extends React.Component {
constructor(){
super();
// 创建了一个 ref,回头我们就可以获取到和该 ref 绑定了的 DOM 节点
this.inputCon = React.createRef();
}
clickHandle = () => {
// 通过 this.inputCon.current 可以获取到 input DOM 节点
console.log(this.inputCon.current.value);
}
render() {
return (
<div>
<input type="text" ref={this.inputCon}/>
<button onClick={this.clickHandle}>获取用户输入的内容</button>
</div>
)
}
}
export default App;
非受控组件的默认值
html
<div>
{/* 一旦你用了 value,React 会认为你这是一个受控组件 */}
{/* 如果想要给默认值,使用 defaultValue */}
<input type="text" ref={this.inputCon} defaultValue="123"/>
<button onClick={this.clickHandle}>获取用户输入的内容</button>
</div>
3、生命周期
所谓生命周期,指的是组件从诞生到销毁会经历一系列的过程,该过程就叫做生命周期。
生命周期钩子函数是属于类组件所独有的东西 ,但是从 React 16.8 推出 Hooks 以来,整体已经开始以函数组件为主,因此仅介绍一些常用的生命周期钩子函数。
常用的生命周期钩子函数如下:
-
constructor
- 同一个组件对象只会创建一次
- 不能在第一次挂载到页面之前,调用 setState ,为了避免问题,构造函数中严禁使用 setState
-
render
- render 是整个类组件中必须要书写的生命周期方法
- 返回一个虚拟 DOM ,会被挂载到虚拟 DOM 树中,最终渲染到页面的真实 DOM 中
- render 可能不只运行一次,只要需要重新渲染,就会重新运行
- 严禁使用 setState,因为可能会导致无限递归渲染
javascript
import React from "react";
// 类组件
class App extends React.Component {
constructor() {
super();
// 主要做一些初始化操作,例如该组件的状态
this.state = {
value : 1
}
console.log("constructor");
}
clickHandle=()=>{
this.setState({
value : this.state.value + 1
})
}
render() {
console.log("render");
return (
<div>
<div>{this.state.value}</div>
<button onClick={this.clickHandle}>+1</button>
</div>
)
}
}
export default App;
- componentDidMount
- 类似于 Vue 里面的 mounted
- 只会执行一次
- 可以使用 setState
- 通常情况下,会将网络请求、启动计时器等一开始需要的操作,书写到该函数中
- componentWillUnmount
- 通常在该函数中销毁一些组件依赖的资源,比如计时器
4、Hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hooks 的出现,首先能解决如下的一些问题:
- 告别令人疑惑的生命周期
- 例如下面的例子,相同的代码在不同的生命周期中存在了两份
js
import React from "react";
// 类组件
class App extends React.Component {
constructor() {
super();
this.state = {
count : 0
}
}
componentDidMount(){
document.title = `你点击了${this.state.count}次`;
}
componentDidUpdate(){
document.title = `你点击了${this.state.count}次`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
)
}
}
export default App;
- 告别类组件中烦人的 this
- 在类组件中,会存在 this 的指向问题,例如在事件处理函数中,不能直接通过 this 获取组件实例,需要修改 this 指向
- 告别繁重的类组件,回归前端程序员更加熟悉的函数
另外,Hooks 的出现,还有更加重要的一个信号,那就是整个 React 思想上面的转变,从"面向对象"的思想开始转为"函数式编程"的思想。这是编程范式上面的转变。
Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
- 只能在函数最外层 调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件 中调用 Hook 。不要在其他 JavaScript 函数中调用。
1、useState 和 useEffect
一个是为函数组件添加状态的 useState ,另一个是处理函数副作用的 useEffect。
useState
javascript
import { useState } from 'react';
function App(props) {
let [age, setAge] = useState(18);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
function clickhandle(){
setAge(++age);
}
return (
<div>
<div>年龄:{age}</div>
<div>水果:{fruit}</div>
<div>待办事项:{todos[0].text}</div>
<button onClick={clickhandle}>+1</button>
</div>
);
}
export default App;
useEffect
useEffect 包含以下知识点:
-
副作用的概念
- 纯函数:一个确切的参数在你的函数中运行后,一定能得到一个确切的值,例如下面的例子:
jsfunction test(x){ return x * 2; } x => 2 ===> 4 x => 3 ===> 6- 如果一个函数中,存在副作用,那么我们就称该函数不是一个纯函数,所谓副作用,就是指函数的结果是不可控,不可预期。
- 常见的副作用有发送网络请求、添加一些监听的注册和取消注册,手动修改 DOM ,以前我们是将这些副作用写在生命周期钩子函数里面,现在就可以书写在 useEffect 这个 Hook 里面
javascript
import { useState, useEffect } from 'react';
function App() {
let [count1, setCount1] = useState(0);
let [count2, setCount2] = useState(0);
let [count3, setCount3] = useState(0);
useEffect(()=>{
console.log("执行副作用函数");
},[count1]);
return (
<div>
<div>count1:{count1}</div>
<div>count2:{count2}</div>
<div>count3:{count3}</div>
<button onClick={()=>setCount1(++count1)}>+1</button>
<button onClick={()=>setCount2(++count2)}>+1</button>
<button onClick={()=>setCount3(++count3)}>+1</button>
</div>
);
}
export default App;
- 如果想要副作用只执行一次,传递第二个参数为一个空数组
js
useEffect(()=>{
console.log("执行副作用函数");
},[]);
2、自定义Hooks
自定义 Hook 的本质其实就是函数,但是和普通函数还是有一些区别,主要体现在以下两个点:
- 自定义 Hook 能够调用诸如 useState 、useRef 等,普通函数则不能。由此可以通过内置的 Hooks 获得 Fiber 的访问方式,可以实现在组件级别存储数据的方案等。
- 自定义 Hooks 需要以 use 开头,普通函数则没有这个限制。使用 use 开头并不是一个语法或者一个强制性的方案,更像是一个约定。
App.jsx
js
import { useState } from 'react';
import useMyBook from "./useMyBook"
function App() {
const {bookName, setBookName} = useMyBook();
const [value, setValue] = useState("");
function changeHandle(e){
setValue(e.target.value);
}
function clickHandle(){
setBookName(value);
}
return (
<div>
<div>{bookName}</div>
<input type="text" value={value} onChange={changeHandle}/>
<button onClick={clickHandle}>确定</button>
</div>
)
}
export default App;
useMyBook
js
import { useState } from "react";
function useMyBook(){
const [bookName, setBookName] = useState("React 学习");
return {
bookName, setBookName
}
}
export default useMyBook;
5、React-router 路由
React-router 是 React 官方所推出的前端路由库,可以分为几大块:
- Components 组件
- Hooks 函数
- API 函数
组件
- BrowserRouter:整个前端路由以 history 模式开始,包裹根组件
- HashRouter:整个前端路由以 hash 模式开始,包裹根组件
- Routes:类似于 v5 版本的 Switch,主要是提供一个上下文环境
- Route:在 Route 组件中书写你对应的路由,以及路由所对应的组件
- path:匹配的路由
- element:匹配上该路由时,要渲染的组件
- Navigate:导航组件,类似于 useNavigate 的返回值函数,只不过这是一个组件
- NavLink:类似于 Link,最终和 Link 一样,会被渲染为 a 标签,注意它和 Link 的区别,实际上就是当前链接,会有一个名为 active 的激活样式,所以一般用于做顶部或者左侧导航栏的跳转
Hooks
- useLocation:获取到 location 对象,获取到 location 对象后,我们可以获取 state 属性,这往往是其他路由跳转过来的时候,会在 state 里面传递额外的数据
- useNavigate:调用之后会返回一个函数,通过该函数可以做跳转。
- useParams:获取动态参数
javascript
import { useRoutes, Navigate } from "react-router-dom";
import Home from "../components/Home";
import About from "../components/About";
import AddOrEdit from "../components/AddOrEdit";
import Detail from "../components/Detail";
import Email from "../components/Email";
import Tel from "../components/Tel";
function Router(props) {
return useRoutes([
{
path: "/home",
element: <Home />,
},
{
path: "/about",
element: <About />,
children : [
{
path : "email",
element : <Email/>
},
{
path : "tel",
element : <Tel/>
},
{
path : "",
element: <Navigate replace to="email" />
}
]
},
{
path: "/add",
element: <AddOrEdit />,
},
{
path: "/detail/:id",
element: <Detail />,
},
{
path: "/edit/:id",
element: <AddOrEdit />,
},
{
path: "/",
element: <Navigate replace to="/home" />
}
]);
}
export default Router;
javascript
import { NavLink } from "react-router-dom";
import RouterConfig from "./router/router"
import "./css/App.css"
function App() {
return (
// 最外层容器
<div id="app" className="container">
{/* 导航栏 */}
<nav className="navbar navbar-inverse navbar-fixed-top">
<div className="container">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<div className="navbar-brand">学生管理系统</div>
</div>
<div id="navbar" className="collapse navbar-collapse">
<ul className="nav navbar-nav">
<NavLink to="/home" className="navigation">主页</NavLink>
<NavLink to="/about" className="navigation">关于我们</NavLink>
</ul>
<ul className="nav navbar-nav navbar-right">
<NavLink to="/add" className="navigation">添加学生</NavLink>
</ul>
</div>
</div>
</nav>
{/* 匹配上的路由所对应的组件显示在这个位置 */}
<div className="content">
<RouterConfig/>
</div>
</div>
);
}
export default App;
使用 Outlet 组件,该组件表示匹配上的子路由组件渲染的位置。
javascript
import React from 'react';
import { NavLink, Outlet } from "react-router-dom"
function About() {
return (
<div className="about container">
<h1 className="page-header">使用说明</h1>
<p>通过此系统来熟悉 react 以及 react router 的使用</p>
<p>联系方式</p>
<NavLink to="/about/email" className="navigation">邮箱</NavLink>
<NavLink to="/about/tel" className="navigation">电话</NavLink>
<Outlet />
</div>
);
}
export default About;
6、React-redux
React 官方在 Redux 的基础上推出了 React-redux ,另外Redux 官方还推出了 Redux Toolkit ,来简化整个 Redux 的使用。因此现在在 React 应用中,状态管理库的使用一般都是 React-redux + Redux Toolkit
首先第一个安装两个依赖,命令如下:
js
npm install @reduxjs/toolkit react-redux
状态管理
所谓状态管理,指的是把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测 。

-
单向数据流 。View 发出 Action (
store.dispatch(action)),Store 调用 Reducer 计算出新的 state ,若 state 产生变化,则调用监听函数重新渲染 View (store.subscribe(render)) -
单一数据源 ,只有一个 Store
-
state 是只读的,每次状态更新之后只能返回一个新的 state
-
没有 Dispatcher ,而是在 Store 中集成了 dispatch 方法,
store.dispatch()是 View 发出 Action 的唯一途径 -
支持使用中间件(Middleware)管理异步数据流
Redux 数据流:

在浏览器中使用 redux 扩展工具,首先需要打开谷歌浏览器的扩展应用商店,下载 redux devtools

接下来需要在创建 store 的地方,添加如下的代码:
js
export const store = createStore(
todoReducer,
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
React-redux原生写法
配置:
store.js
javascript
import { createStore } from "redux";
// 引入 reducer
import { todoReducer } from "./reducers"
// 需要你传入一个 reducer(纯函数,用于计算最新的状态)
export const store = createStore(
todoReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
reducers.js
javascript
// reducer,用于计算最新的状态
import { ADD,DEL,CHANGE } from "./actionType";
// 仓库一开始默认的数据
let defaultState = {
list: [
{
content: "学习 React",
status: false,
},
{
content: "复习 Vue",
status: false,
},
{
content: "玩游戏",
status: false,
},
{
content: "听歌",
status: false,
},
],
};
/**
* 通过这个纯函数我们会计算出最新的状态
* state : 仓库数据,每次会传入上一次的仓库数据
* action : 描述对象 {type : ADD, data : "学习 redux"}
* 描述对象描述了我要做什么,以及带来的额外数据
*/
export function todoReducer(state = defaultState, action) {
// 有了描述对象后,我就可以根据旧状态计算出新的状态并返回
switch (action.type) {
case ADD: {
// 新增的操作
const arr = [...state.list];
arr.push({
content: action.data,
status: false,
});
return { list: arr };
}
case DEL : {
const arr = [...state.list];
arr.splice(action.data, 1);
return { list: arr };
}
case CHANGE : {
const arr = [...state.list];
arr[action.data].status = !arr[action.data].status;
return { list: arr };
}
default: return state;
}
}
action.js
javascript
export const addListAction = newItem => ({
type: ADD,
data : newItem
})
export const delListAction = index => ({
type: DEL,
data : index
})
export const changeAction = index => ({
type: CHANGE,
data : index
})
使用:
index.js
javascript
import ReactDOM from 'react-dom/client';
import App from './App';
// 引入仓库
import { store } from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById('root'));
function render(){
root.render(
<App store={store}/>
);
}
render();
// 让 redux 和 react 建立关联
// subscribe 叫做订阅,在仓库的状态发生改变的时候
store.subscribe(render);
app.js
javascript
import Input from "./components/Input";
import List from "./components/List";
import "./css/App.css"
function App(props) {
return (
// 最外层容器
<div className="container">
<h1 className="lead" style={{
marginBottom : "30px"
}}>待办事项</h1>
<Input store={props.store}/>
<List store={props.store}/>
</div>
)
}
export default App;
List.jsx
javascript
import {delListAction,changeAction} from "../redux/actions"
function List(props) {
// 在 redux,通过 store.getState() 来获取仓库数据
// dispatch 方法会派发一个 action 对象到 reducer 里面
// addListAction(value) ===> { type : ADD, data : value}
// 这个就是我们的 action 描述对象,该对象会被 dispatch(派发)到 reducer 里面
const lis = props.store.getState().list.map((item,index)=>{
return (
<li key={index} className="text-primary">
<span
onClick={()=>props.store.dispatch(changeAction(index))}
className={["item", item.status ? 'completed' : ""].join(" ")}
>{item.content}</span>
<button
type="button"
className='close'
onClick={()=>props.store.dispatch(delListAction(index))}
>×</button>
</li>
)
})
return (
<div>
<ul style={{marginTop : 20}}>
{lis}
</ul>
</div>
);
}
export default List;
React-redux + Redux Toolkit 写法
配置:
store.js
javascript
import { configureStore } from "@reduxjs/toolkit";
import stuReducer from "./stuSlice";
export default configureStore({
reducer: {
stu: stuReducer,
},
});
stuSlice.js
javascript
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import {
getStuListApi,
addStuApi,
} from "../api/stuApi";
// 异步获取所有的学生数据
export const getStuListAsync = createAsyncThunk(
"stu/getStuListAsync",
async (_, thunkApi) => {
// 发送 ajax 请求
const response = await getStuListApi();
// 派发 action
thunkApi.dispatch(initStuList(response.data));
}
);
// 异步新增学生
export const addStuAsync = createAsyncThunk(
"stu/addStuAsync",
async (payload, thunkApi) => {
const { data } = await addStuApi(payload);
// 将这个 data 更新到数据仓库
thunkApi.dispatch(addStu(data));
}
);
export const stuSlice = createSlice({
name: "stu",
initialState: {
stuList: [],
},
reducers: {
// 初始化学生列表到仓库的 stuList 里面
initStuList: (state, { payload }) => {
state.stuList = payload;
},
// 添加学生
addStu: (state, { payload }) => {
state.stuList.push(payload);
},
},
});
const { initStuList, addStu } = stuSlice.actions;
export default stuSlice.reducer;
使用
index.js
javascript
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
Home.jsx
javascript
import { useSelector, useDispatch } from "react-redux";
import { getStuListAsync } from "../redux/stuSlice"
function Home(props) {
// 现在应该是从仓库里面去获取学生的数据
const { stuList } = useSelector(state => state.stu);
const dispatch = useDispatch();
// 注意,这里需要添加依赖性为空数组,代表只执行一次
useEffect(() => {
// 现在应该是从仓库获取数据
if(!stuList.length){
// 如果没有数据,应该发送请求获取数据
// 但是也不是在这里直接发请求,而是应该派发一个 action 到仓库
// 仓库里面负责发送异步请求获取数据,然后将获取到的数据填充到前端仓库
dispatch(getStuListAsync());
}
}, [stuList, dispatch]);
}
Add.jsx
javascript
import { addStuAsync, editStuAsync } from "../redux/stuSlice";
import { useSelector, useDispatch } from "react-redux"
const dispatch = useDispatch();
const { stuList } = useSelector(state => state.stu);
const [stu, setStu] = useState({
name: "",
age: "",
phone: "",
email: "",
education: "本科",
graduationschool: "",
profession: "",
profile: "",
});
function submitStuInfo(e) {
e.preventDefault();
for (const key in stu) {
if (!stu[key]) {
alert("请完善表单的每一项");
return;
}
}
if (id) {
// 编辑
dispatch(editStuAsync({ id, stu }));
navigate("/home", {
state: {
alert: "学生修改成功",
type: "info",
}
});
} else {
dispatch(addStuAsync(stu));
navigate("/home", {
state: {
alert: "学生添加成功",
type: "success",
}
});
}
}
区别
index.js 的变化,需要从 react-redux 中引入 Provider 的组件,用于提供一个上下文环境,包裹应用的根组件,之后仓库会做为 Provider 的 store 属性,不需要再在 App.jsx 根组件上面挂载了
js
// ....
import { Provider } from "react-redux";
// 引入仓库
import store from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
store.js 的变化,从 toolkit 里面引入 configureStore 方法,用于创建我们的数据仓库
js
// 引入创建仓库的方法
import { configureStore } from "@reduxjs/toolkit";
// 调用该方法时,传入一个配置对象
// 其中一个选项是配置 reducer
export default configureStore({
reducer : {
}
});
组件连接仓库的改变,之前使用 redux 的时候,组件还是需要从父组件传递的 props 上面拿到仓库数据,现在可以通过 useSelector 这个 Hook 直接连接仓库
js
const {list} = useSelector(state=>state.todo);
组件向仓库派发 action 时的改变,首先是获取 dispatch 方法的方式,之前使用纯 redux 的时候,dispatch 是通过 store 拿到的,现在是通过 useDispatch 来拿到。
js
dispatch(add(value));
action 之前是通过我们自己书写的 action creator 来创建的,现在是直接从 slice 里面导出即可。
js
export const {add,del,change} = todolistSlice.actions;