一、基础

React 是目前前端大型项目的最佳解决组件化解决方案。

webpack 将所有资源都看成是模块的打包方式,让组件化开发思维有了落地实践的可能,React 则是在此基础上,彻底贯彻了组件化思维。

React 并非一个框架,而是一个 UI 库。它传达的是一种基于组件化的响应式开发思维

INFO

理解并认知这一点非常重要,他决定了你学习 React 的方式是否正确。我们的重心应该是学习这种思维方式,许多人在学习 React 时,往往会忽略这一点,那么学习成果自然是不理想的

React 颠覆了传统的前端开发思维。在前端历史上具有里程碑的意义。

二、响应式思维

在前面章节的模块案例一中,我们使用原生代码结合发布订阅模式,基于模块化开发思维,也实现了一个响应式的交互。这里我们使用 React 重新实现一次看看差别如何。

效果:一堆控制按钮,改变 div 原生的宽高颜色等属性

首先使用 create react app 创建一个项目

code.ts
npx create-react-app controldiv --template typescript

修改 src/app.css 的内容如下

code.ts
1
#control {
2
width: 500px;
3
margin: 100px auto;
4
}
5
6
#control .target {
7
width: 200px;
8
height: 200px;
9
border: 2px solid #FFF;
10
background-color: #cccccc;
11
transition: 0.3s;
12
}
13
#control .target.hide {
14
display: none;
15
}

然后修改 src/App.tsx 如下

code.ts
1
import React, { useRef, useState } from 'react';
2
import './App.css';
3
4
function App() {
5
const [bgColor, setBgColor] = useState('#FFF')
6
const [borderColor, setBorderColor] = useState('#ccc')
7
const [show, setShow] = useState(true)
8
const [width, setWidth] = useState(200)
9
const [height, setHeight] = useState(200)
10
11
const bgColorInput = useRef<HTMLInputElement>(null)
12
const bdColorInput = useRef<HTMLInputElement>(null)
13
14
function bgColorHandler() {
15
if (bgColorInput.current) {
16
setBgColor(bgColorInput.current.value)
17
}
18
}
19
20
function bdColorHandler() {
21
if (bdColorInput.current) {
22
setBorderColor(bdColorInput.current.value)
23
}
24
}
25
26
const style = {
27
backgroundColor: bgColor,
28
borderColor,
29
display: show ? 'block' : 'none',
30
width,
31
height
32
}
33
34
return (
35
<div id="control">
36
<div className="control_wrap">
37
<div><button className="show" onClick={() => setShow(!show)}>show/hide</button></div>
38
<div>
39
<input ref={bgColorInput} className="bgcolor_input" type="text" placeholder="input background color" />
40
<button className="bgcolor_btn" onClick={bgColorHandler}>sure</button>
41
</div>
42
<div>
43
<input ref={bdColorInput} type="text" className="bdcolor_input" placeholder="input border color" />
44
<button className="bdcolor_btn" onClick={bdColorHandler}>sure</button>
45
</div>
46
<div>
47
<span>width:</span>
48
<button className="width_reduce" onClick={() => setWidth(width - 5)}>-5</button>
49
<button className="width_add" onClick={() => setWidth(width + 5)}>+5</button>
50
</div>
51
<div>
52
<span>height:</span>
53
<button className="height_reduce" onClick={() => setHeight(height - 5)}>-</button>
54
<input type="text" className="height_input" readOnly value={height} />
55
<button className="height_add" onClick={() => setHeight(height + 5)}>+</button>
56
</div>
57
</div>
58
<div className="target" style={style}></div>
59
</div>
60
);
61
}
62
63
export default App;

这里我们要关注的核心是,当我们想要修改目标 div 的宽度时,我们仅仅只是使用了 setWidth() 这一句代码。

但实际上我们要做的事情还有很多,获取目标元素,设置目标元素的值等等,但是 React 帮助我们处理了这些事情,于是,当点击事件发生时,我们只需要关注数据的改变就可以了。

通过数据控制 UI 交互,是响应式交互思维的核心。

这极大的减少了我们的开发工作量,我们思考的内容也变得更少了。我们只需要维护好不同时刻的不同数据,就能够完成页面上的交互需求。

因此在实践中,我们会把与 UI 相关的状态与数据,单独使用 React 内部提供的 useState 或者 this.state 来管理。而其他的与 UI 渲染结果无关的数据,则采用别的方式来管理。

二、组件的表现形式与本质

所有的客户端语言都有自己表达页面结构的方式,Android、iOS、Flutter、html 等。而 html 被公认为是最通俗易懂的表达页面结构的方式。

标签语言也提供了组件化的最佳呈现方式。

我们思考一下 html 标签,例如 button 元素。

code.ts
<button>按钮</button>

button 还支持很多属性,disabled,name,value 等等,我们之前称之为 attribute

code.ts
1
<button name="btn">按钮1</button>
2
<button disabled>按钮2</button>

我们知道,不同的属性,对应了不同的功能,样式。也就是说,我们可以通过传入不同的属性控制 button 的能力。

与此同时,button 元素还有自己的内部逻辑。例如点击时,样式会发生改变。

所以,我们要完全的掌握 html 元素,明白这两个很核心的知识点,它们有内部的状态与逻辑,也可以通过外部传入属性控制元素的能力。这刚好就是一个组件应该呈现的样子。

React 组件借鉴 html 元素,并扩展 html 元素。

因此,一个最简单的 React 组件声明如下

code.ts
1
function Button() {
2
return (
3
<button>默认按钮</button>
4
)
5
}

该组件没有内部状态,也没有外部属性,因此使用时也很简单

code.ts
<Button />

那如果我们要自定义一个更复杂的组件,就需要结合实际情况,分别考虑组件内部状态 state,与组件外部属性 props

在 HTML 中,标签只是语法糖,之所以呈现这种结构是因为更容易理解元素之间的相互关系。一个标签元素的本质是对象。也就是说,只要我们在 html 中,写入一个标签,与之对应的,就是在实例化一个该标签的对象。

同样的道理,在 React 组件运用的 jsx 模板中,每一个标签元素都是一个对象。

当我们理解了这一点,在 React 中,使用时如下的写法就很容易理解了

code.ts
const element = <div />
code.ts
1
function Welcome() {
2
return <h1>Hello, wrold!</h1>;
3
}

四、自定义组件

与 html 元素不同的是,React 提供了自定义组件的方式。因此我们可以在此基础上有更丰富的想象力。自定义组件是我们在实践开发中经常要干的事情。

当然,所有的自定义组件,本质都都是基于 html 元素组件。React 重新实现了这些 html 直接支持的元素。

刚才我们已经分析过,当我们要自定义复杂的组件时,需要考虑内部逻辑 state 与外部属性 props

INFO

本文主要的目的不是 React 技术细节的学习,因此我们只介绍函数式创建组件的方式,class 创建组件的方式大家另行学习

自定义一个考虑了 props 逻辑的组件。

code.ts
1
function Welcome(props) {
2
return <h1>Hello, {props.name}</h1>;
3
}

使用时

code.ts
<Welcome name="world" />

自定义一个考虑了内部 state 逻辑的组件,

实现效果:按钮点击一次,按钮显示的数字 + 1

code.ts
1
function Button() {
2
const [number, setNumber] = useState(0)
3
4
return (
5
<button onClick={() => setNumber(number + 1)}>
6
{number}
7
</button>
8
)
9
}

使用时

code.ts
<Button />

五、单向数据流

我们上面在定义 Button 组件时,往 button 中传入了属性 onClick,当然,我们也可以把 Button 内部的 number 传递给子组件。

code.ts
1
function Button() {
2
const [number, setNumber] = useState(0)
3
4
return (
5
<div>
6
<button onClick={() => setNumber(number + 1)}>+ 1</button>
7
<Welcome name={number} />
8
</div>
9
10
)
11
}

这种数据自上而下的传递过程,叫做单向数据流,数据的源头一定存在于某个组件的 state 中,并通过 props 传递。

任何 state 的修改,都会导致数据流动一次,数据流动时,UI 也会重新渲染一次。

因此,当我们想要让两个或者多个组件之间进行数据交互时,最好的方式就是找到这些组件共同的数据源「共同的父级」,在不同的组件之中,借助 props 的流动,通过修改数据源的 state,进行交互。

  1. 找到共同的数据源/也就是共同的父级
  2. 借助 props 向下传递修改修改 state 的方法
  3. 修改 state
  4. 数据源重新流动,借助 props 的变化更改 UI

如果是父子组件,就把 state 存在父组件中。 如果是不相关的两个组件,就找这两个组件共同的父级组件。

推荐资源:

1. React 官方中文文档

专栏首页
到顶
专栏目录