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 勾选上就可以简单看看:

image-20241109162000100

JSX 中使用 JS 表达式

在 JSX 中可以通过大括号 {} 识别 JavaScript 中的表达式,比如常见的变量、函数调用、方法调用等等。常见场景有:

  1. 使用引号传递字符串
  2. 使用 JavaScript 变量
  3. 函数调用和方法调用
  4. 使用 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;

image-20241109163225388

其中 <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 中,可以通过逻辑与运算符 &&、三元表达式 (?:) 实现基础的条件渲染

  1. 逻辑与:{flag && <span>flag为真则显示span</span>}
  2. 三元表达式:{loading ? <span>loading...</span> : <span>show</span>}

复杂条件下,可以使用自定义函数返回不同结果的 jsx 模板来实现需求。

React 基础事件绑定

基础绑定

语法:on + 事件名称 ={事件处理程序},整体上遵循驼峰命名法(如:handleClick)

image-20241109170842883

获取事件

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

image-20241109171126332

传递自定义参数

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

image-20241109171347125

传递自定义参数和事件

image-20241109171510601

warning

请注意自定义参数和事件参数 e 的位置。传递和接收须一一对应。

React 组件

在 React 中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图 UI,渲染组件只需要把组件当成标签书写即可。

image-20241109172427287

warning

请注意声明和使用时,组件名首字母大写。

useState 基础使用

usestate 是 - 一个 ReactHook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果。

本质:和普通 JS 变量不同的是,状态变量一旦发生变化组件的视图 UI 也会跟着变化(数据驱动视图)。

例如:const [count, setCount] = useState(0)

  1. useState 是一个函数,返回值是一个数组。
  2. 数组中的第一个参数是状态变量,第二个参数是 set 函数用来修改状态变量
  3. useState 的参数将作为 count 的初始值

简单案例:

image-20241109173503242

warning

  1. 请先将 useState 进行导入
  2. 请注意使用 setCount 时,传递的值为新值,不能使用 count++ 或者 count += 1 等类似写法。

修改状态的规则

前文提到,使用 setCount 时,传递的值为新值,不能使用 count++ 或者 count += 1 等类似写法。

info

在 React 中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新。

即,你不能使用以下方式更改 count 的值:

image-20241109174316164

于是,修改对象这种复杂类型的状态变量,也同样适用。

复杂类型修改

info

在 React 中,对于对象类型的状态变量,应该始终传给 set 方法一个全新的对象来进行修改。

例如:

image-20241109175314730

其中的 setUser 方法将属性展开,再书写要覆盖的属性进行赋值,这样就完成了实际上的属性修改,如果不展开,那么属性将只剩下后书写的属性。

React 样式控制

一般分为行内写法和分离写法。

行内写法例如:

image-20241109214302038

或者像前面的写法一样,写成:<span style = {{ color: red }}>

分离写法的意思是,将样式抽离为一个 css 文件进行导入使用。具体是在标签上使用 className(此为固定名)进行导入使用。例如:

1
2
3
.foo {
  color: red;
}
image-20241109214659938

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 钩子函数:

  1. 使用 useRef 创建 ref 对象,并与 JSX 绑定
  2. 在 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 父子通信

父传子

  1. 子组件标签上通过属性绑定数据
  2. 子组件通过 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>

运行结果如下:

recording

其他方法 - 待补充

TODO

跨层级通信 - Context

适用于形成顶层 - 底层层级关系的组件通信。

假设有 App、B、C 三个组件。B 组件是被 App 使用的子组件,且 B 组件中又使用 C 组件,则此时 C 组件为底层组件而 App 为顶层组件。

  1. 使用 createContext 方法创建一个上下文对象 Ctx(名称可随意)
  2. 在顶层组件(App)中通过 Ctx.Provider 组件提供数据
  3. 在底层组件(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 函数。

  1. 声明一个以 use 打头的函数
  2. 在函数体内封装可复用的逻辑(只要是可复用的逻辑)
  3. 把组件中用到的状态或者回调 return 出去(以对象或者数组)
  4. 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用

所以,代码也可以是下面这种:

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

  1. hook 函数只能在组件中或者其他自定义 hook 函数中调用
  2. hook 函数只能在组件的顶层调用,不能嵌套在 if、for 或者其他函数中

下面两种调用方式都是错误的:

  1. 组件外调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { 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;
  2. 嵌套在 if 语句中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import { 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),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态

使用步骤:

  1. 定义一个 reducer 函数(根据当前想要做的修改返回一个新的状态)
  2. 使用 createStore 方法传入 reducer 函数生成一个 store 实例对象
  3. 使用 store 实例的 subscribe 方法订阅数据的变化(数据一旦变化,可以得到通知)
  4. 使用 store 实例的 dispatch 方法提交 action 对象触发数据变化(告诉 reducer 你想怎么改数据)
  5. 使用 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 目录结构有一定的安排,可以是:

image-20241114195752266

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 状态的逻辑按照功能进行分割和组织的方式。每个切片通常包含以下几个部分:

  1. 初始状态:定义该切片的初始状态,通常是一个对象。
  2. reducers:一组用于更新该切片状态的函数。每个 reducer 函数定义了如何根据不同的 action 修改状态。
  3. 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:

image-20241114211731362

不过现在只能显示数据,而不能修改。

修改数据

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' // 这是类型自动生成的字符串
}

异步状态操作


React18 学习笔记
https://4rozen.github.io/archives/React/36922.html
作者
4rozeN
发布于
2023年12月5日
许可协议