阅读说明
软件版本
- React v18.3.1
- React Dom v18.3.1
- Vite v5.3.1
- vitejs/plugin-react v4.3.1
- TypeScript v5.2.2
- @types/react v18.3.3
- @types/react-dom v18.3.0
介绍
React是一个开源的前端JavaScript库,用于构建用户界面。用户界面由按钮、文本和图像等小单元内容构建而成。React帮助你把它们组合成可重用、可嵌套的组件。从we端网站到移动端应用,屏幕上的所有内容都可以被分解成组件。
它由Facebook维护,并在2013年首次发布。React的核心特性是其组件化架构,允许开发者使用名为“React组件”的可重用代码片段构建复杂的用户界面。
体验React
直接将以下代码保存在本地,打开页面查看效果。
例子一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function MyApp() {
return <h1>Hello, world!</h1>;
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<MyApp />);
</script>
</body>
</html>
|
在线预览。
例子二
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
|
<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
</body>
<!-- This setup is not suitable for production. -->
<!-- Only use it in development! -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script async src="https://ga.jspm.io/npm:es-module-shims@1.7.0/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react?dev",
"react-dom/client": "https://esm.sh/react-dom/client?dev"
}
}
</script>
<script type="text/babel" data-type="module">
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
let App = function App() {
return <Greeting name="world" />
}
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);
</script>
</html>
|
在线预览。
以上代码仅适合快速了解React的使用。为什么?你可以看到用到了<script type="text/babel">,它是一种在早期React和其他使用ES6模块语法的项目中使用的脚本标记方式。
- Babel是一个广泛使用的JavaScript编译器,它可以将ES6+语法转换为向后兼容的JavaScript语法(通常是 ES5),也可以将JSX解析成JavaScript,这样代码就可以在更多的环境中运行,包括旧版浏览器。
text/babel是一个自定义的MIME类型,它不是一个官方标准,而是一种约定俗成的用法,使用之前确保页面引入了Babel的polyfill和相关设置。
在页面加载时,Babel会自动检测type="text/babel"的脚本,并将其内容转译成ES5代码,然后运行。
这种写法并不是一个长期推荐的解决方案,因为它依赖于全局的Babel配置,可能会与其他构建工具或模块打包器冲突。
现代前端开发通常使用构建工具(如Webpack、Rollup、Vite等)来处理代码转译和打包,这些工具提供了更高效、更灵活、更强大的配置选项和处理方式。
data-type="module"是告诉Babel polyfill该脚本应该被视为模块来处理。
使用构建工具运行React
这里使用Vite。
按照提示处理就行,我这里选的是:
Select a framework: React
Select a variant: TypeScript
接着:
1
2
3
|
cd your_react_project
npm install
npm run dev
|
将得到以下目录:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
├── README.md
├── index.html
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ └── react.svg
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
|
组件
组件是React的核心概念之一,它们是构建用户界面(UI)的基础。一个组件是UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面。
每个React组件都是一个JavaScript函数,它会返回一些标签,React会将这些标签渲染到浏览器上。
是的,你没理解错,React组件会将HTML标签和逻辑耦合在一起。
为什么?官方说法是web交互性越来越强,逻辑越来越决定页面中的内容,JavaScript控制着HTML的内容。
乍眼一看还是很晦涩难懂的,以下是个人的理解看法:
例如JS里可以看到createElement、innerHTML等等,都是做着渲染HTML的事情,这种写法和过程是很冗长很混乱的,React的出现,只是将这个过程用JSX更直观地放在了组件中进行,并且它做的更简单、易懂。再加上都是JavaScript的东西,不需要再学一些特有的东西,结合友好的文档,有JS基础的人,学起来会相对舒服一些。
就像这样:
1
2
3
|
function MyApp() {
return <h1>Hello, world!</h1>;
}
|
这就是React组件的定义方式,MyApp是组件名称,名称必须以大写字母开头,换句话说你可以由此作为依据来辨别React组件,因为HTML标签必须是小写字母开头的。
<h1>Hello, world!</h1>,这段看起来像HTML,实际上是JavaScript,这种语法被称为JSX,它允许你在JavaScript中嵌入标签。JSX看起来和HTML很像,但它的语法更加严格并且可以动态展示信息。
但JSX并不属于React,它不是React的特性,了解更多关于JSX(强烈建议先了解完再回过头来看)。
简单的JSX写在一行没问题,但如果JSX很多,要换行应该怎么写?就像下面这样:
1
2
3
4
5
6
7
|
function MyApp() {
return (
<h1>
Hello, world!
</h1>;
)
}
|
当JSX和return不在同一行的时候,必须用括号把它包裹起来,否则return之后的代码都会被忽略。
使用组件
导出和导入
要使用组件就要先将组件导出,会用到export,它是JavaScript的标准语法,这样你就能够在其他文件中使用import进行导入。
新建一个Buttons.tsx文件。
导出的方式有两种:
- 默认导出:
export default
- 具名导出:
export
一个模块文件中可以有多个具名导出,但只允许有一个默认导出。
默认导出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
function Button({ name }: { name: string } )
{
return (
<button>
{name}
</button>
)
}
function Buttons()
{
return (
<div>
<h1>buttons</h1>
<Button name="submit" />
</div>
)
}
export default Buttons
|
在另一个文件App.tsx中导入:
1
2
3
4
5
6
7
8
|
import Buttons from './Button.tsx'
function App()
{
return (
<Buttons />
)
}
|
具名导出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
export function Button({ name }: { name: string } )
{
return (
<button>
{name}
</button>
)
}
export default function Buttons()
{
return (
<div>
<h1>buttons</h1>
<Button name="submit" />
</div>
)
}
|
另一个文件App.tsx中导入:
1
2
3
4
5
6
7
8
9
10
11
|
import Buttons, { Button } from './Button.tsx'
function App()
{
return (
<>
<Buttons />
<Button name="click me" />
</>
)
}
|
通常,文件中仅包含一个组件时,人们会选择默认导出,而当文件中包含多个组件或某个值需要导出时,则会选择具名导出。
无论选择哪种方式,请记得给你的组件和相应的文件命名一个有意义的名字。不建议创建未命名的组件,比如 export default () => {},因为这样会使得调试变得异常困难。
子组件
通过上面的例子可以看到,Button是一个子组件,同时也是一个函数,它返回了JSX,并嵌套进了Buttons父组件中,然后再被嵌套进App组件中。
这个过程可以实现俄罗斯套娃式嵌套JSX,再联合{}大括号可以将组件玩出花来。
Props
React组件使用props来互相通信。每个父组件都可以提供props给它的子组件,从而将一些信息传递给它。Props可能会让你想起 HTML 属性,但你可以通过它们传递任何JavaScript值,包括对象、数组和函数。
以<img>HTML标签举例,它的属性(在HTML标准里称为attributes)有:
- alt
- src
- width
- height
- srcset
- loading
- crossorigin
而在上面的例子中:
1
|
<Button name="submit" />
|
name就是组件的Props,类似于<img>标签的属性Attributes。
获取Props
1
2
3
4
5
6
7
8
9
10
11
12
|
interface ButtonProps {
name?: string
}
export function Button(props: ButtonProps) {
const name = props.name
return (
<button>
{name}
</button>
)
}
|
可以看到props这个形参,这样就能够接收所有赋值给Button组件的props。
当然,你也可以这么写:
1
2
3
4
5
6
7
8
|
function Button({ name }: { name: string } )
{
return (
<button>
{name}
</button>
)
}
|
function Button({ name }),这种形参的写法叫做解构赋值,是JavaScript的表达式。
渲染列表
1
2
3
4
5
6
7
8
9
10
11
12
|
export default function Ul() {
const names: string[] = ['jayce', 'jack', 'jay'];
return (
<ul>
{
names.map((name) => (
<li key={name}>{name}</li>
))
}
</ul>
)
}
|
也可以这么写:
1
2
3
4
5
6
7
|
export default function Ul() {
const names: string[] = ['jayce', 'jack', 'jay'];
const items = names.map(name => <li key={name}>{name}</li>);
return (
<ul>{items}</ul>
)
}
|
const items = names.map(name => <li>...</li>);这种写法叫做箭头函数表达式。
- 箭头函数
=>之后的表达式会被隐式地返回,因此可以省略return语句。
- 箭头函数
=> {后面的部分被称为块函数体(Block body),它支持多行代码的写法,但必须用显式的return语句返回。
其中<li key={name}>的key属性是必须的,它的值起到唯一标识作用,实际使用不应该是name,而应是id。
因为这些key会告诉React,每个组件对应着数组里的哪一项,所以React可以把它们匹配起来。这在数组项进行移动(例如排序)、插入或删除等操作时非常重要。一个合适的key可以帮助React 推断发生了什么,从而得以正确地更新DOM树。
JSX
JSX和React是两个相互独立的东西,JSX是JavaScript语法扩展,是一种语法扩展,而React是一个JavaScript库。
JSX看起来就是HTML标签,到底和HTML有什么不同?
规则约束
标签必须闭合
<img>这种自闭和的标签,必须书写成<img />。
<li>这种有开始标签,就必须得有结束标签。<li></li>。
使用驼峰命名法给属性命名
JSX最终会被转化为JavaScript,而JSX中的属性也会变成JavaScript对象中的键值对。在你自己的组件中,经常会遇到需要用变量的方式读取这些属性的时候。
但JavaScript对变量的命名有限制。例如,变量名称不能包含-符号或者像class这样的保留字。
这就是为什么在React中,大部分HTML和SVG属性都用驼峰式命名法表示。
例如,需要用strokeWidth代替stroke-width。由于class是一个保留字,所以在React中需要用className来代替。这也是 DOM 属性中的命名。
JSX写法:
1
2
3
4
5
|
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
|
HTML写法:
1
|
<img src="https://i.imgur.com/yXOvdOSs.jpg" alt="Hedy Lamarr" class="photo">
|
如果在编写JSX时有错误,React也会在编译时告诉你。
如果你需要用到aria-和data-这样的属性时,使用驼峰命名法并不适用,这是HTML历史原因。
JSX:
1
2
3
|
<button dataType="ac">
submit
</button>
|
编译以后的HTML:
1
|
<button datatype="ac">submit</button>
|
应该这么写,JSX:
1
2
3
|
<button data-type="ac">
submit
</button>
|
编译以后的HTML:
1
|
<button data-type="ac">submit</button>
|
只能返回一个根元素
如果想要在一个组件中包含多个元素,需要用一个父标签把它们包裹起来。
例如,你可以使用一个 <div> 标签:
1
2
3
4
5
6
|
<div>
<h1>title</h1>
<ul>
...
</ul>
</div>
|
如果你不想在标签中增加一个额外的 <div>,可以用 <> 和 </> 元素来代替:
1
2
3
4
5
6
|
<>
<h1>title</h1>
<ul>
...
</ul>
</>
|
这个空标签被称作Fragment。React Fragment允许你将子元素分组,而不会在 HTML 结构中添加额外节点。
为什么多个 JSX 标签需要被一个父元素包裹?
JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。
以上,不符合规则的写法都不是JSX,如果你有大量的HTML需要转换成JSX,可以用在线转换器。
在JSX中使用JavaScript
动态指定属性的值,需要用到变量,在JSX中可以用{变量名}以此来访问JavaScript变量。
1
2
3
4
5
6
7
8
9
10
11
|
export default function Avatar() {
const avatar = 'https://i.imgur.com/7vQD0fPs.jpg';
const description = 'Gregorio Y. Zara';
return (
<img
className="avatar"
src={avatar}
alt={description}
/>
);
}
|
在{}(大括号)中,可以使用任何JavaScript表达式,例如函数调用、三元表达式、与运算符 &&。
这里举例函数调用:
1
2
3
4
5
6
7
8
9
10
11
|
const title = "hello";
function formatDate(text) {
return text + " world";
}
export default function Title() {
return (
<h1>To Do List for {formatText(title)}</h1>
);
}
|
双大括号{{}},你可能会在别的React应用中看到这种写法,它其实是大括号里的一个对象。
一般会用在写内联样式的时候(实际情况当然是用class类名解决样式问题才更好):
1
2
3
4
5
6
7
8
9
10
11
12
|
export default function TodoList() {
return (
<ul style={{
backgroundColor: 'black',
color: 'pink'
}}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}
|
换一个写法,更容易识别:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
export default function TodoList() {
return (
<ul style={
{
backgroundColor: 'black',
color: 'pink'
}
}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}
|
样式名称background-color遵循驼峰命名规则所以是backgroundColor。
当然,你也可以这么写,这样更简洁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
const data = {
text: 'hello world',
style: {
backgroundColor: 'black',
color: 'pink'
}
};
export default function TodoList() {
return (
<ul style={data.style}>
<li>{data.text}</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}
|
使用限制
在JSX中,只有两种情况可以使用{}大括号:
- 用作JSX标签内的文本:
<h1>{name}</h1> 是有效的,但是 <{tag}>text</{tag}> 无效。
- 用作紧跟在
=符号后的属性:src={avatar}会读取 avatar变量,但是src="{avatar}"只会传一个字符串{avatar}。
结尾
官方文档写的其实已经很好很全面很通俗易懂了,为什么我还会写这下一篇文章?
官方文档乍眼一看还是比较困难的,没有一个概览,对我来说还是散乱了一些,看下来以后发现文档确实写的很好,我做的只是将这些知识重新整理了下顺序,做的相对集中一点,这个过程本身也是一种学习、吸收的方式。
所以你可以看到,文中讲的内容并不全面,并不打算重复文档中已经的东西,虽然是个知识的搬运工,做到切合文章的主题足以。
参考