React18 学习笔记
本文最后编辑于:2024年11月18日 下午
React18 学习笔记
创建 demo 工程
命令为 npx create-react-app react-basic
严格模式
React18 默认创建的工程开启了严格模式。React 应用在开发环境下若使用了 React 的严格模式(<React.StrictMode>
),在渲染过程中,所有组件都会被调用两次。这是为了帮助检测不严谨的代码和潜在的问题。
具体在 src\index.js
中将 <React.StrictMode>
标签去掉即可
1
2
3
4
5
6
7
8
9
10
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
即:
1
2
3
4
5
6
7
8
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
JSX 概念
JSX 是 JavaScript 和 XML(HTML)的缩写,并非标准的 JS 语法,而是一种语法扩展,其表示在 JS 代码中编写 HTML 模版结构,它是 React 中编写 UI 模版的方式。
1
2
3
4
5
6
7
8
9
function App() {
return (
<div className="App">
<h1>Hello World</h1>
</div>
);
}
export default App;
在 React 中,
className
是用来指定 HTML 元素的 CSS 类的属性。由于 JavaScript 语言中class
是一个保留字,因此在 JSX 中使用class
时会出现语法错误。因此,React 使用className
来替代class
。
可以使用在线网站查看 jsx 如何被 babel 转为 js 语法:https://www.babeljs.io
点击网站上方导航栏的 try it out 将 react 勾选上就可以简单看看:
JSX 中使用 JS 表达式
在 JSX 中可以通过大括号 {}
识别 JavaScript 中的表达式,比如常见的变量、函数调用、方法调用等等。常见场景有:
- 使用引号传递字符串
- 使用 JavaScript 变量
- 函数调用和方法调用
- 使用 JavaScript 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const msg = 'Easy React';
function sayMyName() {
return 'The name is John Wick';
}
function App() {
return (
<div className="App">
<h1>Simple Title</h1>
{/** 使用引号传递字符串 */}
{'This is a string'}<br></br>
{/** 使用JS变量 */}
{msg}<br></br>
{/** 函数调用 */}
{sayMyName()}<br></br>
{/** 方法调用 */}
{new Date().toLocaleDateString()}<br></br>
{/** 使用JS对象 */}
<div style={{ color: 'blue' }}>早上好,夜之城。</div>
</div>
);
}
export default App;
其中 <div style={{ color: 'blue' }}>早上好,夜之城。</div>
的 style 的第一层大括号是识别 JS 表达式,用于识别包含 color 属性的对象。
warning
if 语句、switch 语句、变量声明等属于语句,不是表达式,不能出现在大括号 {} 中
JSX 实现列表渲染
渲染需求:
1
2
3
4
5
6
7
<ul>
<li>凯瑟琳:数学家</li>
<li>马里奥:化学家</li>
<li>凯西里:化学家</li>
<li>沃尔特:化学家</li>
<li>苏布拉马妮阳:化学家</li>
</ul>
在 JSX 中可以使用原生 JS 的 map 方法变量渲染:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const list = [
{ name: 'John', age: 25 },
{ name: 'Mary', age: 30 },
{ name: 'Tom', age: 28 },
{ name: 'Jane', age: 23 },
{ name: 'Bob', age: 35 }
]
function App() {
return (
<div className="App">
<h1>Simple Title</h1>
<ul>
{list.map(item =>
<li key={item.name}>{item.name} - {item.age}</li>
)}
</ul>
</div>
);
}
export default App;
warning
key
应当是独一无二的,该值供 React 内部使用,提升列表更新性能
JSX 条件渲染
语法:在 React 中,可以通过逻辑与运算符 &&
、三元表达式 (?:)
实现基础的条件渲染
- 逻辑与:
{flag && <span>flag为真则显示span</span>}
- 三元表达式:
{loading ? <span>loading...</span> : <span>show</span>}
复杂条件下,可以使用自定义函数返回不同结果的 jsx 模板来实现需求。
React 基础事件绑定
基础绑定
语法:on + 事件名称 ={事件处理程序},整体上遵循驼峰命名法(如:handleClick)

获取事件
语法:在事件回调函数中写上形参 e

传递自定义参数
语法:在绑定事件中改写为箭头函数写法,将实参传递

传递自定义参数和事件

warning
请注意自定义参数和事件参数 e 的位置。传递和接收须一一对应。
React 组件
在 React 中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图 UI,渲染组件只需要把组件当成标签书写即可。

warning
请注意声明和使用时,组件名首字母大写。
useState 基础使用
usestate 是 - 一个 ReactHook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果。
本质:和普通 JS 变量不同的是,状态变量一旦发生变化组件的视图 UI 也会跟着变化(数据驱动视图)。
例如:const [count, setCount] = useState(0)
- useState 是一个函数,返回值是一个数组。
- 数组中的第一个参数是状态变量,第二个参数是 set 函数用来修改状态变量
- useState 的参数将作为 count 的初始值
简单案例:

warning
- 请先将 useState 进行导入
- 请注意使用 setCount 时,传递的值为新值,不能使用
count++
或者count += 1
等类似写法。
修改状态的规则
前文提到,使用 setCount 时,传递的值为新值,不能使用 count++
或者 count += 1
等类似写法。
info
在 React 中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新。
即,你不能使用以下方式更改 count 的值:

于是,修改对象这种复杂类型的状态变量,也同样适用。
复杂类型修改
info
在 React 中,对于对象类型的状态变量,应该始终传给 set 方法一个全新的对象来进行修改。
例如:
其中的 setUser 方法将属性展开,再书写要覆盖的属性进行赋值,这样就完成了实际上的属性修改,如果不展开,那么属性将只剩下后书写的属性。
React 样式控制
一般分为行内写法和分离写法。
行内写法例如:

或者像前面的写法一样,写成:<span style = {{ color: red }}>
分离写法的意思是,将样式抽离为一个 css 文件进行导入使用。具体是在标签上使用 className
(此为固定名)进行导入使用。例如:
1
2
3
.foo {
color: red;
}

classnames 类名控制
github 地址:https://github.com/JedWatson/classnames
可以使用 classnames 进行类名控制,解决类名控制时使用模板字符串拼接类名过多时的拼接易出错、不直观的问题。
例如这种类名写法:
1
2
3
4
5
6
7
8
9
10
11
12
function App() {
const type = 'admin'; // 该值可以是接收到的后端数据,这里只是示例
return (
<div className="App">
<span
className={`spanTitle${type === 'admin' && 'active'}`}>
This is John wick.
</span>
</div>
);
}
export default App;
如果使用 classnames 则为:
1
2
3
4
5
6
7
8
9
10
11
12
function App() {
const type = 'admin'; // 该值可以是接收到的后端数据,这里只是示例
return (
<div className="App">
<span
className={classNames('spanTitle', { 'active': type === 'admin' })}>
This is John wick.
</span>
</div>
);
}
export default App;
React 表单受控绑定
概念:使用 React 组件的状态(useState)控制表单的状态。
就是使用 useState 的 value 绑定输入框的 value,再通过 onChange 事件将输入的值传给 set 方法,实现输入数据视图更新的操作。例如:
1
2
3
4
5
6
7
8
9
10
11
12
import { useState } from "react";
function App() {
const [name, setName] = useState('')
return (
<div className="App">
<input type="text" onChange={(e) => setName(e.target.value)}></input>
<span style={name === '' ? { display: "none" } : {}}>Hello, The name is {name}!</span>
</div>
);
}
export default App;
一旦输入框输入值,span 标签将会显示值。
React 获取 Dom
在 React 组件中获取 / 操作 DOM,需要使用 useRef 钩子函数:
- 使用 useRef 创建 ref 对象,并与 JSX 绑定
- 在 DOM 可用时,通过 inputRef.current 拿到 DOM 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useRef } from "react";
function App() {
const domRef = useRef(null);
const showDom = () => {
console.log(domRef.current);
console.dir(domRef.current);
}
return (
<div className="App">
<input type="text" ref={domRef} />
<button onClick={showDom}>获取dom</button>
</div>
);
}
export default App;
React 父子通信
父传子
- 子组件标签上通过属性绑定数据
- 子组件通过 props 形参接收数据对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Son(props) {
return (
<div className="Son">
<h1>This is {props.appName}</h1>
</div>
);
}
function App() {
const name = "John Wick";
return (
<div className="App">
<Son appName={name}></Son>
</div>
);
}
export default App;
请注意子组件中使用的是 appName 属性,而非具体传递的 name 变量。
warning
props 可以传递任意合法的 JS 数据,但 props 是只读的,子组件不能直接修改该数据。
特殊的 props 属性 - children
如果不是在子组件的标签属性中传递数据,而是将内容嵌套在子组件标签中,则子组件将通过一个默认的 props 属性进行接收,该属性名为 children。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Son(props) {
return (
<div className="Son">
{props.children}
</div>
);
}
function App() {
const name = "John Wick";
return (
<div className="App">
<Son>
<h1>hello {name}!</h1>
<span>a child of Son</span>
</Son>
</div>
);
}
export default App;
子传父
核心思想:利用父组件传递的函数传递自己的参数 / 数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { useState } from "react";
function Son(props) {
return (
<div className="Son">
<h1>I am a Son</h1>
<input
type="text"
onChange={(e) => props.onSendMsg(e.target.value)}/>
<button
onClick={() => props.onSendMsg("Hello from Son")}>
send message
</button>
</div>
);
}
function App() {
const [msg, setMsg] = useState('')
const sendMsg = (msg) => {
setMsg(msg)
}
return (
<div className="App">
<span>I am a Parent, and I have a Son who can send me a message: {msg}</span>
<hr />
<Son onSendMsg={sendMsg}></Son>
</div>
);
}
export default App;
如果传递的函数数量较少,也可以直接解构处理它。
React 兄弟通信
很容易想到一个办法是:借助父组件进行数据传递。
朴素的办法 - 状态提升
假设有 B、C 组件为兄弟关系,它们存在一个共同的父组件 App,构建如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { useState } from "react";
function B(props) {
const bMsg = "Hello from B";
return (
<div className="B">
<h2>This is B component</h2>
<p>bMsg: {bMsg}</p>
<button onClick={() => props.AppMsg(bMsg)}>sending bMsg to C</button>
<button onClick={() => props.AppMsg("")}>clearing AppMsg</button>
</div>
);
}
function App() {
const [msg, setMsg] = useState('')
const appClick = (msg) => {
setMsg(msg)
}
return (
<div className="App">
<B AppMsg={appClick}></B>
<hr />
<C AppMsg={msg}></C>
</div>
);
}
function C({AppMsg}) {
return (
<div className="C">
<h2>This is C component</h2>
<p>{AppMsg}</p>
</div>
);
}
export default App;
注意区分给 B 组件和 C 组件传递的 AppMsg 分别是什么:B 为回调函数,C 为来自 B 组件的消息字符串。
1
2
3
<B AppMsg={appClick}></B>
<hr />
<C AppMsg={msg}></C>
运行结果如下:
其他方法 - 待补充
TODO
跨层级通信 - Context
适用于形成顶层 - 底层层级关系的组件通信。
假设有 App、B、C 三个组件。B 组件是被 App 使用的子组件,且 B 组件中又使用 C 组件,则此时 C 组件为底层组件而 App 为顶层组件。
- 使用 createContext 方法创建一个上下文对象 Ctx(名称可随意)
- 在顶层组件(App)中通过
Ctx.Provider
组件提供数据 - 在底层组件(C)中通过
useContext
钩子函数获取消费数据
代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { createContext, useContext } from "react";
const MsgContext = createContext();
function App() {
const AppMsg = "Hello from App.";
const AppMsg2 = "Hello from App2.";
return (
<div className="App">
<MsgContext.Provider value={[AppMsg, AppMsg2]}>
This is App component.
<B></B>
</MsgContext.Provider>
<hr />
</div>
);
}
function B() {
return (
<div className="B">
<span>This is B component</span>
<C></C>
</div>
);
}
function C() {
const bMsg = useContext(MsgContext);
return (
<div className="C">
<span>This is C component. </span>
<span>{bMsg}</span>
</div>
);
}
export default App;
传递多个数据 <MsgContext.Provider value={[AppMsg, AppMsg2]}>
,需要用数组进行传递。
实际上,任何能够形成顶层 - 底层层级的组件关系都可以使用 Context 进行通信。
例如,父子关系也是一种顶层 - 底层关系。
useEffect 概念
useEffect 是一个 React Hook
函数,用于在 React 组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送 AJAX 请求,更改 DOM 等等。
一个简单的场景是:当组件加载完毕之后需要获取数据(发送请求),实现数据渲染。
语法:
1
useEffect(() => {}, [])
- 参数 1 是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作。
- 参数 2 是一个数组(可选),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次。
代码可以是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { useEffect, useState } from "react";
function App() {
const requestUrl = 'https://smart-shop.itheima.net/index.php?s=/api/goods/list&categoryId=10036';
const [resList, setResList] = useState([])
useEffect(() => {
async function fetchData() {
const res = await fetch(requestUrl);
const {data} = await res.json();
console.log(data.list.data);
setResList(data.list.data);
}
fetchData();
}, []);
return (
<div className="App">
<ul>
{resList.map(item => <li key={item.goods_id}>{item.goods_name}</li>)}
</ul>
<hr />
</div>
);
}
export default App;
依赖项的具体影响如下表:
依赖项 | 副作用函数执行时机 |
---|---|
没有依赖项:不写 | 组件初始渲染 + 组件更新时执行 |
空数组依赖:[ ] | 只在初始渲染时执行一次 |
特定依赖项:[example] | 组件初始渲染 + 特定依赖项变化时执行 |
useEffect - 清除副作用
在 useEffect 中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作。比如在 useEffect 中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用。
语法:
1
2
3
4
5
6
useEffect(() => {
...副作用操作逻辑...
return () => {
...清除副作用逻辑...
}
})
也就是在 return 中新写一个函数,该函数最常见的执行时机是在组件卸载时自动执行。
例如,在 Son 组件中开启一个定时器,卸载时清除这个定时器。代码可以是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { useEffect, useState } from "react";
function B() {
useEffect(() => {
let timer = setInterval(() => {
console.log('工作中...');
}, 1000);
return () => {
clearInterval(timer);
timer = null;
}
}, [])
return (
<div className="B"></div>
)
}
function App() {
const [isShow, setIsShow] = useState(true)
return (
<div className="App">
{isShow && <B />}
<button onClick={() => setIsShow(!isShow)}>卸载B</button>
</div>
);
}
export default App;
React 自定义 hook 函数
假设要实现一个将某元素结构隐藏 / 显示的按钮逻辑,代码可以是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useState } from "react";
function App() {
const [isShow, setIsShow] = useState(false);
const handleClick = () => {
setIsShow(!isShow);
};
return (
<div className="App">
{isShow && <h2>This is John Wick.</h2>}
<button onClick={setIsShow}>Toggle</button>
</div>
);
}
export default App;
但很显然,这种切换元素结构隐藏或显示的代码是经常使用的,如果每一次都需要手动再写一次较为麻烦,于是这里引出封装自定义 hook 函数。
- 声明一个以 use 打头的函数
- 在函数体内封装可复用的逻辑(只要是可复用的逻辑)
- 把组件中用到的状态或者回调 return 出去(以对象或者数组)
- 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
所以,代码也可以是下面这种:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useState } from "react";
function useToggle() {
const [isShow, setIsShow] = useState(false);
const toggle = () => {
setIsShow(!isShow);
};
return [isShow, toggle];
}
function App() {
const [isShow, toggle] = useToggle();
return (
<div className="App">
{isShow && <h2>This is John Wick.</h2>}
<button onClick={toggle}>Toggle</button>
</div>
);
}
export default App;
warning
- hook 函数只能在组件中或者其他自定义 hook 函数中调用
- hook 函数只能在组件的顶层调用,不能嵌套在 if、for 或者其他函数中
下面两种调用方式都是错误的:
组件外调用
jsxCopy Code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import { useState } from "react"; function useToggle() { const [isShow, setIsShow] = useState(false); const toggle = () => { setIsShow(!isShow); }; return [isShow, toggle]; } // 这将报错:React Hook “useToggle” 不能在顶层调用。React Hook 必须在 React 函数组件或自定义 React Hook 函数中调用。 const [isShow, toggle, sendMsg] = useToggle(); function App() { return ( <div className="App"> <span>This is a React App.</span> </div> ); } export default App;
嵌套在 if 语句中
jsxCopy Code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { useState } from "react"; function useToggle() {... } function App() { if (Math.random() > 0.5) { // 这将报错 const [isShow, toggle, sendMsg] = useToggle(); } return ( <div className="App"> <span>This is a React App.</span> </div> ); } export default App;
Redux-html 小试牛刀
Redux 是 React 最常用的集中状态管理工具,类似于 Vue 中的 Pinia(或者 Vuex),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态
使用步骤:
- 定义一个 reducer 函数(根据当前想要做的修改返回一个新的状态)
- 使用 createStore 方法传入 reducer 函数生成一个 store 实例对象
- 使用 store 实例的 subscribe 方法订阅数据的变化(数据一旦变化,可以得到通知)
- 使用 store 实例的 dispatch 方法提交 action 对象触发数据变化(告诉 reducer 你想怎么改数据)
- 使用 store 实例的 getState 方法获取最新的状态数据更新到视图中
例如在 Html 中使用 redux,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Html Redux Example</title>
</head>
<body>
<button id="increment">Increment</button>
<span id="counter">99</span>
<button id="decrement">Decrement</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script>
const counter = document.getElementById('counter');
// state管理数据的初始状态,action存放的是触发的动作
function reducer(state = { counter: Number(counter.textContent) }, action) {
if (action.type === 'INCREMENT') {
return { counter: state.counter + 1 } // 不能直接修改state,需要基于原始的state创建一个新的对象
} else if (action.type === 'DECREMENT') {
return { counter: state.counter - 1 }
}
return state // 其他情况返回原来的state
}
const store = Redux.createStore(reducer) // 创建一个Redux的store
store.subscribe(() => { // 每次state发生变化时,都调用这个函数
counter.textContent = store.getState().counter; // 更新页面上的counter
})
const inBtn = document.getElementById('increment');
inBtn.addEventListener('click', () => {
store.dispatch({ type: 'INCREMENT' }) // 触发action
})
const deBtn = document.getElementById('decrement');
deBtn.addEventListener('click', () => {
store.dispatch({ type: 'DECREMENT' })
})
</script>
</body>
</html>
Redux 与 React
在 React 中使用 redux,官方要求安装俩个其他插件:Redux Toolkit 和 react-redux
- Redux Toolkit(RTK)是官方推荐编写 Redux 逻辑的方式,是一套工具的集合集,简化书写方式
- react-redux 是用来链接 Redux 和 React 组件的中间件
安装命令可以是:
1
2
3
4
# 创建项目
npx create-react-app yourProjectName
# 安装插件
npm i @reduxjs/toolkit react-redux
或者使用 vite 构建项目,命令可以是:
1
npm create vite@latest my-vite-app
运行上述命令之后,按照提示选择 create-react-app 模板即可(SWC 是 JavaScript 编译工具比 Babel 要快,但是在功能和插件生态系统方面 Babel 更完善)。
Redux 目录结构安排
安装好 Redux 后,应该对 sotre 目录结构有一定的安排,可以是:
index.js 将 modules 中的 store 导出即可,比较类似 Pinia 的目录安排。
创建一个 counterSotre
编写代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { createSlice } from "@reduxjs/toolkit";
const counterStore = createSlice({
name: "counter",
initialState: {
count: 99
},
// 创建reducer的同时也会自动生成action creators
reducers: {
// 均是同步的,直接修改state
increment: (state) => {
state.count++;
},
decrement: (state) => {
state.count--;
},
// action参数接受额外的参数
incrementByAmount: (state, action) => {
state.count += Number(action.payload);
},
decrementByAmount: (state, action) => {
state.count -= Number(action.payload);
}
}
})
// 解构方便导出使用
const { increment, decrement, incrementByAmount, decrementByAmount } = counterStore.actions;
const reducer = counterStore.reducer;
export { increment, decrement, incrementByAmount, decrementByAmount };
export default reducer;
info
- 为什么创建 Store 的方法为 createSlice?
因为在 Redux 中,"切片"(slice)是指将 Redux 状态的逻辑按照功能进行分割和组织的方式。每个切片通常包含以下几个部分:
- 初始状态:定义该切片的初始状态,通常是一个对象。
- reducers:一组用于更新该切片状态的函数。每个 reducer 函数定义了如何根据不同的 action 修改状态。
- actions:由 reducers 生成的操作,这些操作可以在组件中被触发,以更新状态。
切片的主要目的是提高代码的可维护性和可读性,将不同功能的状态和行为组织到一个地方,避免将所有的状态和逻辑集中在一个大型文件中。这样可以使状态管理更加清晰和模块化。
导出 store
在 store 目录下的 index.js 文件中编写代码如下:
1
2
3
4
5
6
7
8
9
10
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./modules/counterStore";
const store = configureStore({
reducer: {
counter: counterReducer, // 该名称实际上是可以随意的
},
});
export default store;
由于子模块导出时,使用的是 export default
方式,所以可以重新命名为 counterReducer 以供 reducer 管理。此处的 reducer 和子模块的 reducer 类似一群孩子和监护人的关系,各个子模块的 reducer 由此处 index.js 的 reducer 管理。
Store 注入 React
react-redux 负责把 Redux 和 React 链接起来,内置 Provider 组件通过 store 参数把创建好的 store 实例注入到应用,
链接正式建立。
在由 Vite 创建的项目中,找到 src\main.jsx
文件(如果是 npm 创建则是 index.js):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import store from './store' // 导入store
import { Provider } from 'react-redux' // 导入Provider
createRoot(document.getElementById('root')).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
)
使用 Provide 将 App 包裹,并指定好 store 属性,就可以在 React 中使用 redux 了。
在组件中使用 store
找到 App.jsx 修改为如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
import { useSelector } from 'react-redux'
function App() {
const { count } = useSelector(state => state.counter)
return (
<div className="App">
<button onClick={() => console.log('-clicked')}>-</button>
<span>{count}</span>
<button onClick={() => console.log('+clicked')}>+</button>
</div>
)
}
export default App
这个地方的 state 参数 state => state.counter
指的其实是 sotre 的 index.js 文件中的 reducer 中的 counter:

不过现在只能显示数据,而不能修改。
修改数据
React 组件中修改 store 中的数据需要借助另外一个 hook 函数:useDispatch
它的作用是生成提交 action 对象的 dispatch 函数。
App.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment, incrementByAmount } from './store/modules/counterStore'
function App() {
const { count } = useSelector(state => state.counter)
const dispatch = useDispatch()
let amount = 0
function handleAmountChange(e) {
amount = e.target.value
}
return (
<div className="App">
<input type="text" placeholder='Enter amount' onChange={handleAmountChange}/>
<button onClick={() => dispatch(incrementByAmount(amount))}>confirm</button>
<br/>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
)
}
export default App
在这段代码中,使用 dispatch
方法并传入 increment()
时,它实际上是调用导入了的 increment
这个 action creator 函数,这个函数会返回一个 action 对象。例如:
1
2
3
{
type: 'counter/increment' // 这是类型自动生成的字符串
}