Featured image of post React入门

React入门

阅读说明

软件版本

  • 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。

1
npm create vite@latest

按照提示处理就行,我这里选的是:

  • 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里可以看到createElementinnerHTML等等,都是做着渲染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中,只有两种情况可以使用{}大括号:

  1. 用作JSX标签内的文本<h1>{name}</h1> 是有效的,但是 <{tag}>text</{tag}> 无效。
  2. 用作紧跟在=符号后的属性src={avatar}会读取 avatar变量,但是src="{avatar}"只会传一个字符串{avatar}

结尾

官方文档写的其实已经很好很全面很通俗易懂了,为什么我还会写这下一篇文章?

官方文档乍眼一看还是比较困难的,没有一个概览,对我来说还是散乱了一些,看下来以后发现文档确实写的很好,我做的只是将这些知识重新整理了下顺序,做的相对集中一点,这个过程本身也是一种学习、吸收的方式。

所以你可以看到,文中讲的内容并不全面,并不打算重复文档中已经的东西,虽然是个知识的搬运工,做到切合文章的主题足以。

参考

Built with Hugo
主题 StackJimmy 设计