具体安装及引入细节,请直接参考官方文档。
:: React 的出新文档了,内容的组织结构也有所变化,整体来说更加突出函数式组件的作用了,毕竟有了 Hooks 嘛~ 来一个新的章节来摘录一下,准备在新的项目中应用 React ,反正用什么我说了算,哈哈~ ↗️ 「 [[#新文档]] 」
React 是一个用于构建用户界面的 JavaScript 库,你可以用它给简单的 HTML 页面增加一点交互,也可以开始一个完全由 React 驱动的复杂应用。
> 对的,它只是一个 UI 库而已 !!!
在新的项目中,强烈建议直接使用 Vite 来创建你的应用 - npx create vite
,如下:
PS D:\WP> npm create vite
> npx
> create-vite
√ Project name: ... my-react-app
√ Select a framework: » React
√ Select a variant: » JavaScript + SWC
...
当然,你也可以继续使用基于 webpack 的打包方式,但随着你的项目越来越大,其编译速度会越来越慢。
简单的就不说了,直接来看一下 React 团队推荐的创建 SPA (单页面,Single Page App)的工具链 - Create React App 。
要创建项目,请执行:
npx create-react-app my-app
cd my-app
npm start
Create React App 不会处理后端逻辑或操纵数据库;它只是创建一个前端构建流水线(build pipeline),所以你可以使用它来配合任何你想使用的后端。它在内部使用 Babel 和 webpack,但你无需了解它们的任何细节。当然,关于它,你肯定想了解更多,请参考 用户指南 。
如果你倾向于从头开始打造你自己的 JavaScript 工具链,可以 查看这个指南,它重新创建了一些 Create React App 的功能。
新文档
:: 由于 Hook 的引入,在新文档中你基本上看不到
class
组件的写法了。JavaScript 的面向对象应用并不多,回归“函数”是一个非常明智的选择。
老生常谈
React 组件是 返回标签 的 JavaScript 函数。React 组件必须以大写字母开头,而 HTML 标签则必须是小写字母。
:: class 的写法基本上被束之高阁了~
返回标签?什么标签?JSX 喽。
需要注意的是,你的组件 不能返回多个 JSX 标签,你必须将它们包裹到一个共享的父级中,,比如 <div>...</div>
或使用空的 <>...</>
包裹。
在 React 中,你可以使用 className
来指定一个 CSS 的 class ,但它并没有规定你如何添加 CSS 文件。
JSX 会让你把标签放到 JavaScript 中,而大括号会让你 “回到” JavaScript 中。
:: 这一点做的就比较好,只有一个
{...}
搞定一切,统一无负担。
至于条件渲染和列表渲染,则直接使用 javascript 本身的能力即可。
你可以通过在组件中声明 事件处理 函数来响应事件,需要注意的是,不要 调用 事件处理函数(即不要在函数名后加小括号):你只需 把函数传递给事件 即可。
React 引入 useState
这个内置 Hook ,用来初始化和更新组件状态 state 。多次渲染同一个组件,每个组件都会拥有自己的 state。
💡 你只能在你的组件(或其他 Hook)的 顶层 调用 Hook。
老样子,我们可以通过状态容提升来实现组件状态共享。当然,后续我们会有另外的方式。
再谈 React 哲学
React 可以改变你对可见设计和应用构建的思考。当你使用 React 构建用户界面时,你首先会把它分解成一个个 组件,然后,你需要把这些组件连接在一起,使数据流经它们。
步骤一:将 UI 拆解为组件层级结构
一开始,在绘制原型中的每个组件和子组件周围绘制盒子并命名它们。
![[assets/Pasted image 20240724152435.png|525]]
现在你已经在原型中辨别了组件,并将它们转化为了层级结构。
![[assets/Pasted image 20240724153059.png]]
步骤二:使用 React 构建一个静态版本
先构建一个静态版本,然后再一个个添加交互。构建一个静态版本需要写大量的代码,并不需要什么思考; 但添加交互需要大量的思考,却不需要大量的代码。
:: 这是一种很不错的方式,让大脑的线程不至于来回切换。
步骤三:找出 UI 精简且完整的 state 表示
考虑将 state 作为应用程序需要记住改变数据的最小集合,即组织 state 最重要的一条原则是保持它 DRY(不要自我重复)。
如何判断哪些是 state ,哪些不是呢?
- 随着时间推移 保持不变?如此,便不是 state。
- 通过 props 从父组件传递?如此,便不是 state。
- 是否可以基于已存在于组件中的 state 或者 props 进行计算?如此,它肯定不是state!
步骤四:⭐ 验证 state 应该被放置在哪里
步骤五:添加反向数据流
:: 原型分组件,组件定层级,数据流其中。
旧文档
i.e. 核心概念
老规矩,上 "Hello World"
😂
|
|
它将在页面上展示一个 “Hello, world!” 的标题。不要着急,马上你就明白它的工作原理了!
JSX 简介
再观察一下上面的例子,这是什么?
const element = <h1>Hello, world!</h1>;
> 怎么把 DOM 元素直接赋给了一个变量 ❓
这个有趣的标签语法既不是字符串也不是 HTML。它被称为 JSX,是一个 JavaScript 的语法扩展。
JSX 可以生成 React “元素”,它其实一个表达式,在编译(通过 Babel)之后,会被转为普通 JavaScript 函数(React.createElement()
)调用,并且对其取值后得到 JavaScript 对象。
JSX 的语法格式十分简单!上 🌰
|
|
在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。
只需要注意:
- 尽量将内容包裹在括号中,以避免多行书写时遇到自动插入分号陷阱;
- 在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号;
- 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
OK,这就是 JSX ,再来一个例子看看它的具体转译过程!
|
|
是的,JSX 就是这么简单 ❗
元素渲染
在上一节中,我们已经多次提到了 React “元素” ,它究竟是什么呢?
元素描述了你在屏幕上想看到的内容。如 element
就是一个 React 元素:
const element = <h1>Hello, world</h1>;
与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象(详见上节)。React DOM 会负责更新 DOM 来与 React 元素保持一致。
> 那 React DOM 到底是如何渲染 React 元素为 DOM 的呢 ❓
只需要把它们传入 ReactDOM.render()
就可以了(该元素会被自动渲染到根 DOM 节点中)!
需要注意的是, React 元素是不可变对象! 一旦被创建,你就无法更改它的子元素或者属性。
如何更新 UI 呢?
根据我们已有的知识,更新 UI 唯一的方式是创建一个全新的元素,并将其传入 ReactDOM.render()
。React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。
|
|
当然,在实践中,我们并不会那么蠢,大多数 React 应用只会调用一次 ReactDOM.render()
,后续我们将学习如何封装一个有状态的组件。
组件 & Props
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
在 React 中,有两种组件形式:函数组件和类组件,如下:
|
|
上述两个组件在 React 里是等效的。 它们返回的都是 React 元素哦!
:: 在实际应用中,函数式组件明显更受欢迎,也更符合直觉,再加上现在有了 Hook,所以你懂得 ……
例如,这段代码会在页面上渲染 “Hello, Sara”:
|
|
让我们来回顾一下这个例子中发生了什么:
- 我们调用
ReactDOM.render()
函数,并传入<Welcome name="Sara" />
作为参数; - React 调用 Welcome 组件,并将
{name: 'Sara'}
作为props
传入; - Welcome 组件将
<h1>Hello, Sara</h1>
元素作为返回值; - React DOM 将 DOM 高效地更新为
<h1>Hello, Sara</h1>
。
注意: 组件名称必须以大写字母开头 !!!(React 会将以小写字母开头的组件视为原生 DOM 标签)
组件可以在其输出中引用其他组件(组件组合)。有时候,将组件拆分为更小的组件也是很不错的选择(组件提取)。
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
:: 其实,props 很简单,就把它理解为一个只读的函数入参就行了!函数,你足够了解的,对吧?
Props 是不可变的,但应用程序的 UI 是动态的,并会伴随着时间的推移而变化,emm… 😟
放心!在下一章节中,我们将介绍一种新的概念,称之为 “state”。在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。
State & 生命周期
在元素渲染章节中,我们只了解了一种更新 UI 界面的方法,通过调用 ReactDOM.render()
来修改我们想要渲染的元素。
我们也说了,那种方法有点蠢 🤣! 在本章节中,我们将学习如何封装真正可复用的组件。
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
下面,让我们看一个完整的 Clock 组件(请留意注释内容
):
|
|
让我们来快速概括一下发生了什么和这些方法的调用顺序:
- 当
<Clock />
被传给ReactDOM.render()
的时候,React 会调用Clock
组件的构造函数。因为Clock
需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化this.state
。我们会在之后更新state
; - 之后 React 会调用组件的
render()
方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配Clock
渲染的输出; - 当
Clock
的输出被插入到 DOM 中后,React 就会调用ComponentDidMount()
生命周期方法。在这个方法中,Clock
组件向浏览器请求设置一个计时器来每秒调用一次组件的tick()
方法; - 浏览器每秒都会调用一次
tick()
方法。 在这方法之中,Clock 组件会通过调用setState()
来计划进行一次 UI 更新。得益于setState()
的调用,React 能够知道state
已经改变了,然后会重新调用render()
方法来确定页面上该显示什么。这一次,render()
方法中的this.state.date
就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM; - 一旦 Clock 组件从 DOM 中被移除,React 就会调用
componentWillUnmount()
生命周期方法,这样计时器就停止了。
是的!State 就是一个组件的核心!!! 下面我们来看一下,如何正确的使用它!
|
|
数据是向下流动的!
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。
组件可以选择把它的 state 作为 props 向下传递到它的子组件中。
事件处理
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串;
- 在 React 中另一个不同点是你不能通过返回
false
的方式阻止默认行为,你必须显式的使用preventDefault
。
|
|
另外,当你使用 ES6 class 语法定义一个组件的时候,通常的做法是将事件处理函数声明为 class 中的方法。如下:
|
|
你必须谨慎对待 JSX 回调函数中的 this
,在 JavaScript 中,class 的方法默认不会绑定 this
。如果你忘记绑定 this.handleClick
并把它传入了 onClick
,当你调用这个函数的时候 this
的值为 undefined
。
这并不是 React 特有的行为,这其实与 JavaScript 函数工作原理有关。
:: emm… this 可以说是 JavaScript 永远的痛了,好在应用起来并不算太难!
在事件处理中,除了 this
的绑定之外,还有一个需要注意的地方 - 向事件处理程序传递参数。
在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:
|
|
上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。
在这两种情况下,React 的事件对象 e
会被作为第二个参数传递。 如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind
的方式,事件对象以及更多的参数将会被隐式的进行传递。
条件渲染
这里,就不多讲了,只要记住 JSX 最终会被转成一个 JavaScript 对象,条件渲染也就是 if
或者条件运算符那点事了。
在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render
方法直接返回 null
,而不进行任何渲染。在组件的 render
方法中返回 null
并不会影响组件的生命周期。
想要了解更多,直接阅读 条件渲染 。
列表 & Key
同上,略!
唯一需要注意的是 key
,它是什么?
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
元素的 key 只有放在就近的数组上下文中才有意义。
:: 所谓列表,就是利用一些迭代数据,组装出可用子元素集合,然后把它们放在应该放的父元素中就可以了。
详见 列表 & Key 。
表单
主要是弄清楚 “受控组件” 和 “非受控组件” 的概念,就可以喽。详见 表单 。
状态提升
:: 抽象和共享,永远不变的真理!
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
更多详见 状态提升。
组合 vs 继承
详见 组合 vs 继承 – React - react.docschina.org。
React 哲学
我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。
:: emm… Vue:我才是,Angular:你们都是弟弟!
OK,上心法 ❤️。
第一步:将设计好的 UI 划分为组件层级
第二步:用 React 创建一个静态版本
第三步:确定 UI state 的 最小(且完整)表示
通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:
- 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
- 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
- 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
第四步:确定 state 放置的位置
第五步:添加反向数据流
:: 基础的核心概念并不多(毕竟就是一个 UI 库嘛),但其思想非常好,官方文档也相当 OK ,可以不定期地多看几遍。
HOOK
::不着急,先过几遍再说这个,很简单的!
相关技术栈
当然,你可以选择从零开始,但更好的选择是使用官方提供的脚架 - Create React App 。
样式
React 对样式如何定义并没有明确态度;如果存在疑惑,比较好的方式是和平时一样,在一个单独的 *.css
文件定义你的样式,并且通过 className
指定它们。
React 并没有原生提供 CSS 封装方案!!!
React 本身的设计原则决定了其不会提供原生的 CSS 封装方案,或者说 CSS 封装并不是 React 框架本身的关注点。因此,React 社区从很早的时候就开始寻找相关替代办法。
- CSS 模块化(CSS Modules)
这种做法非常类似 Angular 与 Vue 对样式的封装方案,其核心是以 CSS 文件模块为单元,将模块内的选择器附上特殊的哈希字符串,以实现样式的局部作用域。对于大多数 React 项目来说,这种方案已经足够用了。
- 基于共识的人工维护的方法论,如 BEM
这种方法的缺点是会为团队带来很大的挑战,对于全局和局部规划选择器的命名,团队对于这种方法需要有共识,即使熟练使用的情况下,在使用中依然有着较高的思维负担和维护成本。
- Shadow DOM
借助 direflow.io 等工具,我们可以将 React 组件输出为 Web Component,借助 Shadow DOM 实现组件的 CSS 样式封装。这是一种解决办法,不过基本很少有项目选择这样做。
- CSS-in-JS
- SCSS
好吧,相信你的项目是由 Create React App (CRA) 生成的,如果你想使用 SCSS ,只需要安装 dart-sass
库即可,像下面这样:
npm i sass --save-dev
感谢 node-sass
退出历史舞台,但感谢作者的贡献 😅!
OK,安装之后,就可以把 *.scss
文件作为一个模块引入了,如:
import example from './example.scss';
- CSS in JS
注意此功能并不是 React 的一部分,而是由第三方库提供。
“CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义。在 此处 阅读 CSS-in-JS 库之间的对比。
:: CSS in JS 的本质就是写行内样式 style ❓❗
|
|
当然,大项目,这样直接写是非常不明智的,好在有懒人包 🥳!
目前比较流行的两个解决方案是 styled-components 和 Emotion 。
相关参考:
- CSS Modules 🏆(首推)
这种做法非常类似 Angular 与 Vue 对样式的封装方案,其核心是以 CSS 文件模块为单元,将模块内的选择器附上特殊的哈希字符串,以实现样式的局部作用域。对于大多数 React 项目来说,这种方案已经足够用了。
由于一般的脚手架都默认集成了 CSS Modules,比如 React 官方的脚手架:create-react-app,已经将 CSS Modules 集成进来了,我们可以直接使用。
如何使用呢?
关于样式污染和穿透
在 react中sass的使用,解决样式污染,样式穿透 - 掘金 ?
使用 CSS Modules !最佳实践 如下:
- 每个组件的根节点使用 CSS Modules 形式的类名(根元素的类名:
root
) - 其他所有的子节点,都全用普通的 CSS 类名
:global
|
|
|
|
路由
Hmmm… 页面路由,大大的有用!
- Home v6.26.0 | React Router
- 在开始之前 | React Router6 中文文档
- react-router-dom 使用指南(最新 V6.0.1) - 知乎 - zhuanlan.zhihu.com
- Introduction · React Router 中文手册 - uprogrammer.cn
如果你使用服务端渲染 SSR 的话,你可以直接使用 Next、Remix 等既有的路由框架。
如果准备使用传统的客户端渲染(多数情况),直接使用 React Router 即可,如下:
# npm install react-router-dom # always need this!
import * as React from "react";
import * as ReactDOM from "react-dom/client";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import "./index.css";
const router = createBrowserRouter([
{
path: "/",
element: <div>Hello world!</div>,
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
看,很简单吧。当然,实际项目中,把 router 逻辑单独提取出来作为一个模块引入将使你的代码更加简洁。
状态管理
异步请求
示例
这里我们附上一个基本的脚手架搭建示例,它使用以下技术:
- Vite
- TypeScript
- SWC
- React Router
- SCSS
- Aixos
- Redux
- …
具体内容跳转到 [[React 基础脚手架示例]] 。