内容介绍
本专题假设你已经具备了基本的 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;
状态与事件
在继续写代码之前,让我们来思考两个问题:应该要创建哪些状态,以及应该使用何种事件?
创建状态有两个方案,一是为每个表单输入项创建一个对应的状态:
表单项 | 状态 |
---|---|
姓名 | 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 的隐式转换,当一个字符串和一个数字相乘时,总是返回数字。
本节代码:axum-rs-react-controlled-component
非受控组件
知道了受控组件是和状态绑定的,那么显然非受控组件是不和状态绑定了。问题不是状态绑定,该如何获取表单的值呢?react 提供了 refs
,用于将某个变量与表单项建立关系,然后通过这个变量就可以获取到的变量值了。react 把这种通过 refs
处理表单的组件称为非受控组件。
在有些课程里,会把
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,这样就能在正确的时机重新渲染页面。但既然用了状态,为什么还要用非受控组件?
- 第4行:
const els = { name: null, age: null, region: null };
这是一个普通变量,而不是状态,用来关联表单项 - 第24、34、41行:
ref={(el) => (els.name = el)}
使用回调形式,将 ref 和普通变量相关联