简介
大背景:
- 起源于
Facebook
的内部项目,因为对市面上所有JS MVC框架不满意,就自己开发了一套,用来开发Instagram项目。(开源时间:2013年5月)
三句话解释:
- 是用于构建 Web 和原生交互界面的库。
- 是由各种组件,组成页面。
- 是用于构建UI,专注于视图层的JS框架
主要特点:
- 组件化编码:
- 具有更强的复用性,维护性、扩展性
- 高效(Diff算法):
- 使用虚拟(Virtual)DOM,提升渲染性能
- 使用Diff算法,提升更新效率
- 单向数据流:
- 助于简化数据的管理和维护, 提高代码的可维护性和可预测性
- JSX:
- JavaScript 的语法扩展(JavaScript XML)
- 逻辑与UI耦合,采取组件单元,实现关注点分离
下面,我们再来与Vue进行对比,更为深入地了解React的特点
React Vue 异同处
相同点
- 都有
虚拟Dom + Diff算法
的渲染机制,用于提升渲染速度 - 都使用
组件化开发
,通过props传参等方式进行父子数据通信 - 都具备
状态管理
(Vue的Vuex/Pinia,React的Redux/Mobx) - 都支持
跨平台开发
(Vue的Uniapp,React的React Native)
不同点
框架层面:
- Vue:
- 本质属于
MVVM
模式(由MVC发展出来的) - 由于MVVM模式,
双向数据流
- 本质属于
- React:
- 严格意义上,只能算是
MVC
中的View层 - 由于MVC模式,
单向数据流
- 严格意义上,只能算是
数据层面:
- Vue:
- vue推崇响应式,实现了数据的
双向绑定
。 - 由于
数据可变
,当数据发生变化,可通过 getter/setter 以及一些函数的劫持
监听数据的变化 - 并且当数据变化时,可直接更新到对应的虚拟Dom(VM的理念)
- vue推崇响应式,实现了数据的
- React:
- React推崇
不可变(Immutable)
,为单向数据流
。 - 由于
数据不可变
,所以React无需监听数据的变化,React只对setState之后会有重新渲染的流程 - 当数据发生变化(setState之后),React默认是通过比较
引用的方式(Diff)
进行的,所以若是不优化,则会导致大量非必要渲染,从而影响性能(代码要求更高)
- React推崇
渲染层面:
- Vue:
- Vue可以很快的计算出虚拟DOM的差异,这由于它监听了每一个组件的依赖关系,不需要重新渲染整个组件树
- React:
- React数据变化时,会将全部子组件重新渲染。但可
shouldComponentUpdate
这个生命周期函数进行控制。
- React数据变化时,会将全部子组件重新渲染。但可
Diff 算法:
- Vue:
- 同层比较新老:新的不存在旧的存在就删除,新的存在旧的不存在就创建
- 基于Snabbdom库,使用双向链表,
边对比,边更新DOM
。
- React:
- 递归同层比较,标识差异点保存到Diff队列,从而得到patch树,再
统一操作批量更新DOM
。
- 递归同层比较,标识差异点保存到Diff队列,从而得到patch树,再
其他层面:
- 模板语法不同:Vue是指令+模板语法,react是函数式编程
- 性能优化可控:Vue性能优化(自动的)相对React可控性低,因此大型应用,数据量庞大,无需过量非必要的Watcher,出于性能方面,推荐使用可控的React。
- 社区生态差异:Vue国内受众人群多,React国际受众人群多(更为成熟↑)
核心
接下来,我们继续来了解React的核心内容:
- JSX语法:嵌入绑定、条件渲染、列表渲染
- 组件化:组件分类、组件生命周期、组件通信
- Hooks:useState、useEffect、useContext、useReducer、useRef、useMemo、useCallBack
JSX语法
我们在简介里,已经了解到,JSX全称为JavaScript XML,是JavaScript的一种语法扩展。本质是 React.createElement
的语法糖,即创建虚拟DOM的方法
。
解决痛点: 为了简化创建虚拟DOM
,无需每次嵌套使用 React.createElement。
简易写法:
const element = <h1>Hellow World!!!<h1/>
语法规范:
- JSX只能有一个根标签
- 标签使用变量(JS表达式),用大括号{}包起来
- 类名class,变className使用
- 内联样式,使用键值对写法,例如style={{color:‘#fff’}}
- 标签首字母
- 小写字母开头:html标签
- 大写字母开头:JSX组件
简易例子
const MyComponent = () => {
const divStyle = {
color: 'blue',
backgroundColor: 'yellow',
padding: '10px',
border: '1px solid black'
};
return (
<>
<div className="divClass">外链样式</div>
<div style={divStyle}>内嵌样式</div>
</>
);
};
export default MyComponent;
嵌入绑定
JSX可嵌入变量和表达式(运算符、函数调用)
,绑定属性和方法
。
import React, { useState } from 'react';
const MyComponent = () => {
// 嵌入变量
const name = 'React Developer';
const age = 25;
// 使用useState来管理一个状态变量
const [visible, setVisible] = useState(true);
// 嵌入表达式 - 函数调用(例如,计算年龄的平方)
const ageSquared = () => age * age;
const handleClick = () => {
setVisible(!visible);
}
return (
<>
{/* 嵌入变量 */}
<p>Name: {name}</p>
<p>Age: {age}</p>
{/* 嵌入表达式 - 运算符 */}
<p>Age Squared: {age * age}</p>
{/* 嵌入表达式 - 三元 */}
<p>Is Age Blow 20: {age < 20 ? 'Yes' : 'No'}</p>
{/* 嵌入表达式 - 函数调用 */}
<p>Age Squared: {ageSquared()}</p>
{/* 绑定属性(事件处理器) */}
<button onClick={handleClick}>
{visible ? 'Hide' : 'Show'}
</button>
</>
);
}
export default MyComponent;
条件渲染
常见的条件渲染,有以下三种方式:
- 方法1:
条件函数判断
:适合逻辑多,拆解成函数,进行if,switch 或策略模式 - 方法2:
三元运算符
:适合逻辑简单的,最好就单层。 - 方法3:
与运算符&&
:条件成立渲染后面的标签或组件。
import React, { useState } from 'react';
import Admin from './Admin'; // 假设Admin组件在'./Admin'文件中
const MyComponent = () => {
const [userRole, setUserRole] = useState(1);
const changeUserRole = (role) => {
setUserRole(role === 1 ? 2 : 1)
}
// 函数条件判断
const roleComponents = () => {
if(userRole === 1) return <Admin />
else return null;
}
return (
<>
<button onClick={changeUserRole}>切换身份</button>
{/* 条件函数 */}
{roleComponents()}
{/* 三元运算 */}
{role === 1 ? <Admin /> : null}
{/* 与运算 */}
{role === 1 && <Admin />}
</>
)
}
export default MyComponent;
列表渲染
我们一般使用 map函数
来遍历数组,如下面的代码所示:
function MyComponent() {
const lists = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
return (
<>
<h1>Fruit List</h1>
<ul>
{lists.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</>
);
}
export default MyComponent;
注:key 和 React 中的 Diff 算法密切相关。所以我们需要给每个map列表循环加上key值。
不然会报错:warning: Each child in a list should have a unique “key” prop.
组件化
组件化是一种高效的处理复杂应用系统,更好的明确功能模块作用的方式。即为一种分而治之的思想。
而React整个框架都围绕着组件化这一核心概念展开,可以说组件化是React的核心思想。
- 组件是构建用户界面的基本单元
- React组件可以分为两类:类组件和函数组件
- 组件化的好处:提高代码的可维护性、以及可复用性(提效)
类组件
React一开始,以类组件作为主体,在React16.8版本(引入React Hooks
)后,解决了类组件的部分痛点(Hooks部分再说明),函数组件后来者居上。
定义特点:
- 组件首字母大写
- 继承自React.Component
- 必须实现render函数
实现方式:
- 使用ES6 Class语法
- 构造器是可选的,用于初始化数据
import React, { Component } from 'react';
export default class App extends Component {
constructor() {
super();
this.state = {};
}
render() {
return (
<div>
<h1>我是类组件</h1>
</div>
);
}
}
函数组件
函数组件,字面意思,就是JavaSript函数作为React组件。
定义特点:
- 组件首字母大写
无生命周期函数
,可用useEffect替代无状态管理
,可用useState替代
// 定义一个函数组件
function Greeting({ name }) {
// 使用JSX来渲染组件的UI
return (
<div>
Hello, {name}!
</div>
);
}
// 在另一个组件或应用中使用Greeting组件
function App() {
return (
<div className="App">
<Greeting name="Alice" />
<Greeting name="Bob" />
</div>
);
}
export default App;
生命周期
生命周期,指的是一个组件从其创建到销毁的整个过程。(React里面,只有类组件有,函数组件没有生命周期)
在这个过程中,React为组件划分了多个不同的阶段,每个阶段都配备了专门的方法和用途。正确理解和运用React的生命周期,对于构建既稳定又易于维护的React应用来说,显得至关重要。
掌握并恰当应用这些生命周期方法,将使得我们的React应用更加健壮和高效。
下面是最新16.3版本后的生命周期函数:
挂载时
挂载时,是指组件实例被创建,首次插入到页面的过程。主要涉及到四个方法:constructor
、render
和 componentDidMount
,还有16.3新增的 getDerivedStateFromProps
以下是使用这些方法的示例:
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 1、初始化state
this.state = {
data: props.initialData, // 假设从props中接收initialData
};
}
// 2、静态方法
static getDerivedStateFromProps(props, state) {
// 当props变化时,根据新的props和当前的state更新state
if (props.initialData !== state.data) {
return {
data: props.initialData,
};
}
// 如果没有变化,返回null表示不更新state
return null;
}
componentDidMount() {
// 3、组件挂载到DOM后执行,适合发起网络请求、订阅事件等操作
console.log('Component has been mounted!');
}
render() {
// 4、渲染组件
return (
<div>
<h1>My Component</h1>
<p>Data: {this.state.data}</p>
</div>
);
}
}
export default MyComponent;
以上例子,总结:
constructor(props)
:方法用于初始化组件的state,并绑定事件处理函数到组件实例上。getDerivedStateFromProps
:是一个静态方法,它在每次组件的props更新并且即将重新渲染前被调用。render
:用于渲染组件的UI。componentDidMount
:在组件挂载到DOM后立即执行。
更新时
当React组件的props或state发生变化时,组件会经历更新阶段。在更新阶段,React会根据这些变化决定是否重新渲染组件,并调用相应的生命周期方法。
主要涉及这几个方法:getDerivedStateFromProps
、shouldComponentUpdate
、getSnapshotBeforeUpdate
和 componentDidUpdate
以下是一个例子,说明了React组件在更新时涉及的主要生命周期方法:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
prevCount: null,
};
}
static getDerivedStateFromProps(props, state) {
// 根据props更新state,比如初始化state或者响应props的变化
if (props.initialCount !== state.count) {
return {
count: props.initialCount,
prevCount: state.count,
};
}
return null;
}
shouldComponentUpdate(nextProps, nextState) {
// 根据props或state的变化来决定是否重新渲染组件
// 这里我们简单地比较count是否变化
return nextState.count !== this.state.count;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 在DOM更新之前获取一些信息
// 假设我们要获取滚动位置
const scrollPosition = this.listRef.scrollTop;
return {
scrollPosition,
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
// DOM更新后,使用snapshot中的信息
if (snapshot && snapshot.scrollPosition !== undefined) {
this.listRef.scrollTop = snapshot.scrollPosition;
}
// 假设我们还需要比较prevCount和currentCount
if (prevState.prevCount !== null && prevState.prevCount !== this.state.count) {
console.log(`Count changed from ${prevState.prevCount} to ${this.state.count}`);
}
}
handleIncrement = () => {
this.setState((prevState) => ({
count: prevState.count + 1,
prevCount: prevState.count,
}));
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleIncrement}>Increment</button>
<div
ref={(element) => (this.listRef = element)}
style={{ height: '200px', overflowY: 'scroll' }}
>
{/* Some long content here that causes scrolling */}
</div>
</div>
);
}
}
export default MyComponent;
以上例子,总结:
getDerivedStateFromProps
用于根据传入的props(initialCount
)来初始化或更新组件的state。如果initialCount
与当前count
不同,它会返回一个新的state对象,其中包含更新后的count
和旧的count
值(prevCount
)。shouldComponentUpdate
是一个方法,用于根据props或state的变化来决定是否重新渲染组件。这里我们简单地比较了nextState.count
和this.state.count
,如果它们不同,则组件会重新渲染。getSnapshotBeforeUpdate
在DOM更新之前被调用,它返回一个对象,该对象在componentDidUpdate
的第三个参数中可用。这里我们获取了滚动列表的滚动位置。componentDidUpdate
在组件更新后被调用,并且接收上一个props、上一个state和getSnapshotBeforeUpdate
返回的snapshot作为参数。我们使用snapshot中的滚动位置来恢复滚动位置,同时比较prevCount
和this.state.count
来记录变化。
卸载时
卸载时阶段指的是组件从DOM中被完全移除时。
例如在类组件中,componentWillUnmount
是一个在组件卸载及销毁之前直接调用的生命周期方法。你可以在这个方法中执行任何必要的清理操作,例如取消网络请求、清除定时器、解除事件监听器等。
class MyComponent extends React.Component {
componentDidMount() {
// 设置定时器
this.timer = setInterval(() => {
console.log('This runs every second');
}, 1000);
}
componentWillUnmount() {
// 清除定时器
clearInterval(this.timer);
}
render() {
return <div>MyComponent</div>;
}
}
export default MyComponent;
在上面的例子中,componentDidMount
中设置了一个定时器,而 componentWillUnmount
中则清除了这个定时器,以确保在组件卸载时不会继续执行定时器的回调。
Hooks 放下一篇文章里,这里字已经水的差不多了😀
总结
温故而知新,可以为师矣~
重温React,不仅是对知识的回顾,更是对技术的敬畏和热爱。感谢React带给我们的每一次成长与收获,期待在未来的开发中,我们能与React一起创造更多的奇迹。