内容介绍
本专题假设你已经具备了基本的 HTML 5 、Javascript(ES6) 和 CSS 3 知识。本专题是为了补全 AXUM 全栈开发所需要的技术栈,而不是从零开始的前端课程,如果你还不具备以上知识,请通过其它途径学习完成之后,再来学习本专题。React: 简介与安装
本章将通过编程世界通用的“Hello World”程序,告诉你:创建 React 应用的几种方法、虚拟DOM、JSX以及将 React 挂载到真实 DOM 的步骤。- 支持试读
React:类式组件、函数式组件及state和props
本章将讨论 React 的类式组件、函数式组件以及 React 两个最重要的属性:`state` 和 `props`。 React: 组件的生命周期
本章将讨论 React 组件的常用生命周期。- 支持试读
React: 事件处理、表单处理及受控组件与非受控组件
本章讨论 React 的事件处理,以及表单处理时涉及两个策略:受控组件与非受控组件。 React: 流程控制与key
本章将讨论 React(JSX) 的条件判断和循环。React: 自定义组件及组件通讯
本章我们将正式开启 React 组件之路。虽然我们之前章节的都叫组件,但整个应用只有一个组件,略显单薄。同时,我们还将讨论组件之间如何进行通讯。React: 路由
本章将讨论 React 的路由:通过路由,你可以制作出“多页面”的系统。React: 状态共享
本章将通过一个非常典型的案例来讨论 React 的状态共享:只有登录用户才能看到某些内容。早期的 React 完全依靠其生态中的 redux 等第三方库来实现状态共享;现在 React 提供了官方的 `Context` 来实现这一目的。React: SEO挑战、服务端渲染及本地存储
经过紧张的学习,React 课程终于暂告一个段落了。本章将是一个相对轻松的内容,我们一起探讨一下 React 应用的 SEO 以及为什么需要服务端渲染,同时对 React 课程做个简单的小结。NextJS: 简介与安装
NextJS 是一个 React 框架,它提供了很多有用的功能把 React 的力量发挥地淋漓尽致。本章我们将开始 NextJS 之旅,首先自然是安装它,然后来一个「你好,NextJS」NextJS: 渲染模式和数据获取
趁你现在对我们刚刚讨论的有关 React 和 SEO 的问题还保持有较强的印象,我们先来讨论 NextJS 是如何利用多种渲染模式来应用 SEO 挑战的。NextJS: 内置组件及自动路由
NextJS 既然是 React 的框架,自然提供了一些内置组件来扩展 React。本章将介绍几个常用的 NextJS 内置组件,同时也将介绍 NextJS 的路由系统。NextJS: 开发博客系统
本章我们将使用 NextJS 开发一个小型的博客系统,你将学习到如何从远程服务器获取数据以及数据过滤、NextJS 常用组件的用法、NextJS 的自动路由等功能。Tailwind:简介与响应式设计的基本原则
TailwindCSS 是一款响应式的、移动设备优先的 CSS 工具类框架。本章对响应式布局、移动设备优先、断点等基本概念进行简要说明;并讨论几种安装 tailwind 的方式。Tailwind: 通过小示例体验它
本章通过两个小示例来体验一下 tailwind 的魔力。Tailwind: 默认配置
Tailwind 定义了一系列变量,比如颜色、大小、间距等。本章将介绍 tailwind 的一些默认设置。Tailwind: 撸一个按钮
本章将带你使用 tailwind 撸一个按钮Tailwind: 使用 flex 和 grid 进行响应式布局
本章我们将讨论使用 `flex` 和 `grid` 进行响应式布局,以及为什么不建议再使用 `float` 进行布局。- 支持试读
Tailwind: 撸一个带图标和动画效果的下拉框
本章将使用 tailwind 实现一个没有任何 Javscript 代码的纯 CSS 的下拉框,把应用到导航栏、菜单栏时,也被称为下拉菜单。同时我们将讨论如何在 tailwind 中使用图标,包括图标的进化史:从字体文件到SVG。 Tailwind: 撸一个报价卡片
本章我们将使用 tailwind 撸一个报价卡片。Tailwind: 撸一个响应式的纯CSS导航栏
本章将使用 tailwind 撸一个响应式的、纯 CSS 的导航栏。Tailwind: 集成到React/NextJS
本章将介绍如何将 Tailwind 集成到 React 或 NextJS 项目中。Tailwind: 复用
本章通过将之前撸的按钮改成 NextJS 版,进而讨论 Tailwind 的复用原则。Tailwind: 配置和插件
本章将讨论如何配置 tailwind,以及几个 tailwind 官方插件。Tailwind: 制作响应式博客
本章我们使用 tailwind 将之前课程中 NextJS 迷你博客改造为响应式的布局。
React: 事件处理、表单处理及受控组件与非受控组件
本章讨论 React 的事件处理,以及表单处理时涉及两个策略:受控组件与非受控组件。
事件处理
正如官方文档说的那样:
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
下面通常一个简单的示例来开启事件处理的序幕:
- 有两个组件(用的都是纯 HTML 元素):显示信息的
div
,和改变状态的button
- 初始状态下,显示的信息是
欢迎来到AXUM中文网
- 当点击按钮时,显示的信息变为
AXUM中文网的网址是:https://axum.rs
编写初始页面
import React from 'react';
function App() {
return (
<div>
<div style={{ color: 'red', fontSize: '2rem' }}>欢迎来到AXUM中文网</div>
<input type="button" value="给我变" />
</div>
);
}
export default App;
- 不知道你有没有忘记最重要的一条规范:有且只有一个根元素。因为我们实际是有两个元素,所以需要将它们包裹在一个父元素上
- 还记得
style
该怎么写吗?style={{key:value}}
- 和组件一样,CSS 样式的名称也是遵循小驼峰式命名。
- 让我们来看一下原生 HTML 的写法和 JSX 写法的对比:
- 原生HTML:
<div style="color:red; font-size: 2rem;">你好帅</div>
- JSX:
<div style={{ color: 'red', fontSize: '2rem' }}>你好帅</div>
- 原生HTML:
- 另一条规范是:所有元素必须闭合,比如第7行的
input
style={{key:value}}
- 和组件一样,CSS 样式的名称也是遵循小驼峰式命名。
- 让我们来看一下原生 HTML 的写法和 JSX 写法的对比:
- 原生HTML:
<div style="color:red; font-size: 2rem;">你好帅</div>
- JSX:
<div style={{ color: 'red', fontSize: '2rem' }}>你好帅</div>
- 原生HTML:
- 原生HTML:
<div style="color:red; font-size: 2rem;">你好帅</div>
- JSX:
<div style={{ color: 'red', fontSize: '2rem' }}>你好帅</div>
import React, { useState } from 'react';
function App() {
const [msg, setMsg] = useState('欢迎来到AXUM中文网');
return (
<div>
<div style={{ color: 'red', fontSize: '2rem' }}>{msg}</div>
<input type="button" value="给我变" />
</div>
);
}
export default App;
通过 useState
,我们加入了 msg
状态和与之相关的 setMsg
方法
给按钮绑定单击事件
import React, { useState } from 'react';
function App() {
const [msg, setMsg] = useState('欢迎来到AXUM中文网');
function btnClickHandler() {
setMsg('AXUM中文网的网址是:https://axum.rs');
}
return (
<div>
<div style={{ color: 'red', fontSize: '2rem' }}>{msg}</div>
<input type="button" value="给我变" onClick={btnClickHandler} />
</div>
);
}
export default App;
- 第6行:定义一个函数,它用来处理按钮的单击事件。这个函数非常简单:直接将
msg
的值进行设置。 - 第12行:
onClick={btnClickHandler}
将按钮的单击事件绑定到第6行定义的函数
事件源
我们知道,在原生DOM中,可以通过事件处理函数的参数获取到事件源,幸运的是在 React 中也是一样。
更改事件处理函数的签名
import React, { useState } from 'react';
function App() {
const [msg, setMsg] = useState('欢迎来到AXUM中文网');
function btnClickHandler(e) {
console.log(e.target);
setMsg('AXUM中文网的网址是:https://axum.rs');
}
return (
<div>
<div style={{ color: 'red', fontSize: '2rem' }}>{msg}</div>
<input type="button" value="给我变" onClick={btnClickHandler} />
</div>
);
}
export default App;
我们只需要简单的给事件处理函数加上一个形参,比如第6行的形参 e
。通过该参数就能获取到事件源,比如第7行。
获取发起事件的元素的值
import React, { useState } from 'react';
function App() {
const [msg, setMsg] = useState('欢迎来到AXUM中文网');
function btnClickHandler(e) {
console.log(e.target);
alert(`按钮的文本是:${e.target.value}`);
setMsg('AXUM中文网的网址是:https://axum.rs');
}
return (
<div>
<div style={{ color: 'red', fontSize: '2rem' }}>{msg}</div>
<input type="button" value="给我变" onClick={btnClickHandler} />
</div>
);
}
export default App;
通过 e.target.value
即可获取到发起事件的按钮的文本内容,如第8行所示。
本节代码:axum-rs-handling-events-2
表单处理
和原生 DOM 类似,要处理表单的提交事件,我们可以给表单的 onSubmit
绑定事件,需要注意的是,为了避免原生 DOM 上的表单提交行为(比如刷新页面等),我们需要在事件处理函数中阻止它的默认的行为:
function submitHandler(e) {
e.preventDefault();
}
React 有两种表单处理模式:
- 如果是使用
state
与表单进行绑定,称之为受控模式。此时组件被称为受控组件 - 如果不是使用
state
与表单进行绑定,称之为受控模式。此时组件被称为非受控组件 - 你可以单一的使用以上两种模式的某一个;也可以将两种模式结合使用
- 除非你明确知道原因,否则首选受控模式
下面两节,将通过案例来演示使用受控组件和非受控组件实现相同的预期结果。现在先对这个案例进行简要说明:
- 表单组件:用于输入用户信息:姓名、年龄、地区;以及提交按钮
- 信息组件:将输入的用户信息显示出来
受控组件
React 把那些使用 state
和表单进行绑定的组件称之为受控组件。
初始页面
import React from 'react';
function App() {
return (
<div>
<form>
<div>
<label>
姓名:
<input type="text" placeholder="输入你的姓名" />
</label>
</div>
<div>
<label>
年龄:
<input type="number" placeholder="输入你的年龄" />
</label>
</div>
<div>
<label>
地区:
<select>
<option value="">--请选择--</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
</select>
</label>
</div>
<div>
<button>提交</button>
</div>
</form>
</div>
);
}
export default App;
都是最基础的 HTML,这里不再说明。
状态与事件
在继续写代码之前,让我们来思考两个问题:应该要创建哪些状态,以及应该使用何种事件?
状态
创建状态有两个方案,一是为每个表单输入项创建一个对应的状态:
表单项 | 状态 |
---|---|
姓名 | name |
年龄 | age |
地区 | region |
对应的JSX大概是这样的:
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [region, setRegion] = useState('');
如果是有10个、20个,甚至100个表单项呢?
我觉得更合理的方案是,所有表单项由一个状态维护,我们可以把表单项作为这个状态的每个键值对来处理:
const [formData, setFormData] = useState({
name: '',
age: 0,
region: '',
});
事件
选定了状态的方案,下面思考另一个问题:如何为每个表单项选择合适的事件呢?我们再往回想,我们给每个表单项选择事件的原因是什么——因为我们使用的是受控组件,每个表单项的值都与对应的状态相关联,所以在表单值发生改变时,必须反馈给对应的状态;相反,某个状态发生改变时,也要反馈给对应的表单项。所以,我们的目标是找出某个事件,能在表单项的值发生改变时,及时触发,然后就可以在该事件触发时,更新状态。
看似最常用的文本框,在选择合适的事件时反而显示有点没思路。让我们成下拉框来看。很明显,下拉框应该使用 onChange
事件:每当选择不同的项时,都会触发该事件。
回到文本框,它当然也能在修改值之后触发 onChange
,更进一步,它还能监听键盘事件,比如按下某个键、松开某个键等等。对于本案例而言,onChange
就足够了。
元素/表单项 | 事件 |
---|---|
表单 | onSubmit |
姓名 | onChange |
年龄 | onChange |
地区 | onChange |
提交按钮 | 全自动传递给表单,无需单独设置事件 |
实现受控组件
分析了一波之后,我们可以实现受控组件了:
import React, { useState } from 'react';
function App() {
// 定义状态
const [formData, setFormData] = useState({
name: '',
age: 0,
region: '',
});
// 事件处理
function onNameChangeHandler(e) {}
function onAgeChangeHandler(e) {}
function onRegionChangeHandler(e) {}
function onFormSubmitHandler(e) {
e.preventDefault();
}
return (
<div>
<form onSubmit={onFormSubmitHandler}>
<div>
<label>
姓名:
<input
type="text"
placeholder="输入你的姓名"
value={formData.name}
onChange={onNameChangeHandler}
/>
</label>
</div>
<div>
<label>
年龄:
<input
type="number"
placeholder="输入你的年龄"
value={formData.age}
onChange={onAgeChangeHandler}
/>
</label>
</div>
<div>
<label>
地区:
<select value={formData.region} onChange={onRegionChangeHandler}>
<option value="">--请选择--</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
</select>
</label>
</div>
<div>
<button>提交</button>
</div>
</form>
</div>
);
}
export default App;
- 第5~9行,定义了维护表单数据的状态,每个表单项对应一个键值对
- 第12~17行,定义了所需要的事件处理函数
- 第20行,绑定了表单的
onSubmit
事件的处理函数 - 第27~28行,设置了“姓名”和状态的关联关系,同时绑定了
onChange
事件的处理函数 - 第38~39行,设置了“年龄”和状态的关联关系,同时绑定了
onChange
事件的处理函数 - 第46行,设置了“地区”和状态的关联关系,同时绑定了
onChange
事件的处理函数
显示表单输入的内容
import React, { useState } from 'react';
function App() {
// ...
return (
<div>
<form onSubmit={onFormSubmitHandler}>
{/* ... */}
</form>
<ul>
<li>姓名:{formData.name}</li>
<li>年龄:{formData.age}</li>
<li>地区:{formData.region}</li>
</ul>
</div>
);
}
export default App;
为了显示表单输入的内容,我们加了一个 ul
,很简单的显示各个表单项的内容
实现表单项的事件处理函数
import React, { useState } from 'react';
function App() {
// ...
// 事件处理
function onNameChangeHandler(e) {
setFormData({ ...formData, name: e.target.value });
}
function onAgeChangeHandler(e) {
setFormData({ ...formData, age: e.target.value * 1 });
}
function onRegionChangeHandler(e) {
setFormData({ ...formData, region: e.target.value });
}
// ...
}
export default App;
我们来看一下事件处理函数是如何实现的:
以 onNameChangeHandler
为例:
function onNameChangeHandler(e) {
setFormData({ ...formData, name: e.target.value });
}
- 为什么要用
{...formData}
,而不是直接赋值?如果你知道什么是深拷贝和浅拷贝,那么我要告诉你的是,这里是为了深拷贝。你可以看一下这个文档。 - 还记得
e.target.value
是什么含义吗?忘的话往上翻。
onAgeChangeHandler
中,因为我们期望的年龄是数字,而 HTML 传递过来的总是字符串,所以我们需要将其转换为数字。这里用了小技巧:Javascript 的隐式转换,当一个字符串和一个数字相乘时,总是返回数字。
非受控组件
知道了受控组件是和状态绑定的,那么显然非受控组件是不和状态绑定了。问题不是状态绑定,该如何获取表单的值呢?react 提供了 refs
,用于将某个变量与表单项建立关系,然后通过这个变量就可以获取到的变量值了。react 把这种通过 refs
处理表单的组件称为非受控组件。
在有些课程里,会把
refs
放在和state
、props
相同的地位,并称为“React 三大对象”。我认为不尽然,refs
的地位显然无法和state
、props
相提并论,没有refs
大不了就用受控组件,依然能愉快地玩耍,反之如果没有state
或props
将寸步难行。
在有些课程里,会把 refs
放在和 state
、props
相同的地位,并称为“React 三大对象”。我认为不尽然,refs
的地位显然无法和state
、props
相提并论,没有 refs
大不了就用受控组件,依然能愉快地玩耍,反之如果没有 state
或 props
将寸步难行。
创建 refs
有几种方式,这里使用回调方式:
import React from 'react';
function App() {
const els = { name: null, age: null, region: null };
function onFormSubmitHandler(e) {
e.preventDefault();
const msg = `
姓名:${els.name?.value}\n
年龄:${els.age?.value * 1}\n
地区:${els.region?.value}\n
`;
alert(msg);
}
return (
<div>
<form onSubmit={onFormSubmitHandler}>
<div>
<label>
姓名:
<input
type="text"
placeholder="输入你的姓名"
ref={(el) => (els.name = el)}
/>
</label>
</div>
<div>
<label>
年龄:
<input
type="number"
placeholder="输入你的年龄"
ref={(el) => (els.age = el)}
/>
</label>
</div>
<div>
<label>
地区:
<select ref={(el) => (els.region = el)}>
<option value="">--请选择--</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
</select>
</label>
</div>
<div>
<button>提交</button>
</div>
</form>
</div>
);
}
export default App;
由于使用了非控组件,所以不想引入新的状态。而 React 只有的状态改变时才会重新渲染页面,这就导致目前无法让ul
正常显示输入的内容,所以本例改了直接使用 alert
来显示。
你可以使用状态来关联 ref,这样就能在正确的时机重新渲染页面。但既然用了状态,为什么还要用非受控组件?
你可以使用状态来关联 ref,这样就能在正确的时机重新渲染页面。但既然用了状态,为什么还要用非受控组件?