内容介绍
本专题假设你已经具备了基本的 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 组件之路。虽然我们之前章节的都叫组件,但整个应用只有一个组件,略显单薄。同时,我们还将讨论组件之间如何进行通讯。
自定义组件
何谓自定义组件?我们之前章节都在自定义组件——App
就是。在实际项目中,可能有多个功能,不可能把它们全堆在 App
里,而是需要把不同的功能封装成不同的组件,然后在 App
里调用。
以上一章最后的那个带“正在载入”的用户列表为例,它至少可以拆解成两个组件:
Loading
:显示“正在载入”UserList
:显示用户列表
更进一步,你会发现,用户列表中有多个用户,所以可以继续拆解。最后,将其拆解成以下组件:
组件 | 说明 |
---|---|
App | 主组件 |
Loading | “正在载入”组件 |
UserList | 用户列表组件 |
UserItem | 单个用户组件 |
明确了这些,我们开始拆解。动手之前,我想说一下 React 项目的目录结构:
- 通常组件会放在单独的
compontents
目录 - 每个组件是一个单独的
*.jsx
文件 - 如果组件包含子组件,则创建和组件同名目录
index.jsx
提供入口组件的定义Xxxx.jsx
提供子组件的定义
index.jsx
提供入口组件的定义Xxxx.jsx
提供子组件的定义
由此,我们的目录将变为:
- src
- App.jsx -- 主组件
- compontents -- 组件目录
- Loading.jsx -- “正在载入”组件
- Users -- 用户相关的组件
- index.jsx -- 用户列表组件
- UserItem.jsx -- 单个用户组件
Loading
组件
import React from 'react';
export default function Loading() {
return <div>正在载入</div>;
}
Loding
组件很简单,就一个提示文本。
UserItem
组件
- 第3行:我们给这个函数式组件声明了一个形参,这样它就可以通过
props
参数,从父组件那里得到需要的数据 - 第4行:这是 ES6 的解构写法,rust 也有类似的写法。它和
const user = props.user;
等价 - 第6~8行:显示用户信息。细心的你可能发现了(当然,我知道你没这么细心),在第 6 行没有
key
属性。这是因为,在父组件的循环中才有必要给 key 属性(当然,你同时在这里设置 key 也行,但不要和父组件设置的重复)
UserList
组件
import React, { useEffect, useState } from 'react';
import UserItem from '../Users/UserItem';
export default function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
// 模拟从后端获取数据
setTimeout(() => {
setUsers([
{ id: 123, name: 'axum.rs-1', age: 18 },
{ id: 456, name: 'axum.rs-2', age: 14 },
{ id: 789, name: 'axum.rs-3', age: 21 },
{ id: 1011, name: 'axum.rs-4', age: 28 },
{ id: 1213, name: 'axum.rs-5', age: 38 },
{ id: 1415, name: 'axum.rs-6', age: 58 },
{ id: 1617, name: 'axum.rs-7', age: 82 },
{ id: 1819, name: 'axum.rs-8', age: 12 },
{ id: 2021, name: 'axum.rs-9', age: 13 },
{ id: 2223, name: 'axum.rs-10', age: 33 },
]);
}, 3000);
}, []);
return (
<ul>
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
}
- 第2行:从当前目录导入
UserItem
组件 - 第28行:由之前的直接
li
改成了我们自定义的UserItem
属性- 并且使用了
user.id
来设置每个UserItem
的key - 同时,传递了名为
user
的属性给子组件,属性的值是map
里面的user
- 并且使用了
- 并且使用了
user.id
来设置每个UserItem
的key - 同时,传递了名为
user
的属性给子组件,属性的值是map
里面的user
App
组件
import React from 'react';
import Loading from './compontents/Loading';
import UserList from './compontents/Users';
function App() {
return (
<div>
<h1>用户列表</h1>
<Loading />
<UserList />
</div>
);
}
export default App;
现在的 App
组件是相当清爽了。
- 第2、3行:分别导入
Loading
组件和UserList
组件 - 第9、10行:分别使用
Loading
和UserList
组件
运行之后发现,无论是否完成加载,Loading
组件一直都在。下面将逐步解决这个问题。
本节代码:axum-rs-react-compontents
自定义组件如何传递标签体的内容
我们先来看一个问题,在 App
中,如何让 Loading
显示不同的内容?答案很明显,使用 props
,假设想通过 msg
属性来指定内容:
// App.jsx
<Loading msg="正在载入" />
// Loading.jsx
import React from 'react';
export default function Loading(props) {
const {msg} = props;
return <div>{msg}</div>;
}
这当然是没问题的,但能不能这样:
// App.jsx
<Loading>正在载入</Loading>
你会发现,根本没用。如何让它正常工作呢?React 会自动往 props
插入一个名为 children
的属性,它就是你要的:
// App.jsx
<Loading>正在载入</Loading>
// Loading.jsx
export default function Loading(props) {
const {children} = props;
return <div>{children}</div>;
}
既然知道了 ES6 支持解构,那我们可以这样写:
父子组件的通讯
所谓父子组件的通讯,是指在父子组件之间共享数据及交互。
父组件到子组件的通讯
我们已经用到了这种通讯方式:
App
调用Loading
时:<Loading>正在载入</Loading>
UserList
调用UserItem
时:<UserItem key={user.id} user={user} />
你会发现父组件到子组件的通讯非常简单,使用 props
传递属性即可。
子组件到父组件的通讯
那么子组件如何发送数据给父组件呢?好像没办法,其实不然。你要明白的是:
props
既然可以传递字符串(<Loading msg="正在载入"/>
),也可以传递对象(<UserItem key={user.id} user={user} />
),为什么不能传递函数呢?——你要知道,Javascript可是所谓的万物皆对象,函数也是对象。
好的,明白了,props
可以传递函数,那么然后呢?然后就可以将父组件定义的某个函数传递给子组件,而这个传递过去的函数就可以对数据进行操作,实现子组件到父组件的通讯。
我们回到遗留的问题:如何在用户列表完成数据加载之后,让“正在载入”的提示文本消失。
根据上面的分析,我们可以知道:使用子组件到父组件通讯——当UserList
完成数据加载之后,通过 App
传递给它的回调函数通知 App
。
具体实现可以有很多种,请读者根据我们给出的示例进行发散思维
App
维护isLoading
状态和设置该状态的函数setLoading
- 当调用
UserList
的时候,通过props
将setLoading
传递给它 - 当
UserList
加载完数据之后,调用App
传递过来的setLoading
函数,通知App
组件数据加载完成 App
根据isLoading
状态决定是否显示Loading
组件
App
组件
import React, { useState } from 'react';
import Loading from './compontents/Loading';
import UserList from './compontents/Users';
function App() {
const [isLoading, setLoading] = useState(false);
return (
<div>
<h1>用户列表</h1>
{isLoading && <Loading>载入中。。。</Loading>}
<UserList setLoading={setLoading} />
</div>
);
}
export default App;
UserList
组件
import React, { useEffect, useState } from 'react';
import UserItem from '../Users/UserItem';
export default function UserList({ setLoading }) {
const [users, setUsers] = useState([]);
useEffect(() => {
setLoading(true);
// 模拟从后端获取数据
setTimeout(() => {
setUsers([
{ id: 123, name: 'axum.rs-1', age: 18 },
{ id: 456, name: 'axum.rs-2', age: 14 },
{ id: 789, name: 'axum.rs-3', age: 21 },
{ id: 1011, name: 'axum.rs-4', age: 28 },
{ id: 1213, name: 'axum.rs-5', age: 38 },
{ id: 1415, name: 'axum.rs-6', age: 58 },
{ id: 1617, name: 'axum.rs-7', age: 82 },
{ id: 1819, name: 'axum.rs-8', age: 12 },
{ id: 2021, name: 'axum.rs-9', age: 13 },
{ id: 2223, name: 'axum.rs-10', age: 33 },
]);
setLoading(false);
}, 3000);
}, []);
return (
<ul>
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
}
- 第4行:从
props
里接收父组件传递过来的setLoading
函数 - 第8行:获取数据之前,调用
setLoading(true)
,开启“正在载入” - 第23行:获取数据完成之后,调用
setLoading(false)
,关闭“正在载入”
本节代码:axum-rs-react-compontents-communicate-1
兄弟组件及深层嵌套组件的通讯
对于简单的兄弟组件(组件嵌套一两层),完全可以使用上面的父子组件的通讯方式进行通讯。
随着项目的开发,组件嵌套可能会越来越深,如果还使用父子组件通讯的方式进行通讯,势必会造成:
- 重复的
props
传递链 - 臃肿的主组件——其中很多都只是为了通讯
基于此,可以选择:
- 基于订阅的通讯,比如
pubsub-js
- 使用集中式的通讯——状态共享
由于本人更倾向使用状态共享,所以给出一个简单的 pubsub-js
示例。至于状态共享,将在后面的章节进行讨论。