Intro to React
前端历史
在讲react之前,我们先用十几分钟的时间回顾下前端的发展史,这更有利于我们理解react的产生背景和目前流行的原因
洪荒时期
如图所示,早期的浏览器非常简陋,1995年JavaScript刚建立的时候,页面是没有状态的,此时一个网页的开发遵循后端为主的MVC模式,后端负责保存和管理数据(Model),处理数据,实现业务逻辑(Control),而前端只负责写网站视图层的模板代码(View),此时确实非常符合切图仔的说法,如果大家了解php和jsp的话,肯定能理解以下两个图例。
php
jsp
Ajax的出现
之前前后端不分离技术的痛点
- 用户体验不流畅
- 每次操作都要刷新页面获取新的状态,体验割裂
- 页面加载速度慢
- 每次刷新页面会导致资源重新请求加载,增加加载时间
- 前后端协作困难
举个例子:在用户书写表单的这种情况下,每次提交都要刷新页面,然后在新的页面告诉你成功/失败的状态,如果网速慢或者其他原因,就会导致404,从头重新填写数据,用户体验非常糟糕。
Ajax技术的出现后改变了这个局面,Ajax可以做到在不刷新页面状态,向后端请求数据,更新部分网页,在用户体验上比之前是质的飞跃。这时逐渐前端开始保存一些状态,通过Ajax获取数据后实现某些交互,前后端分离的开发模式雏形开始出现。
但是在这时也存在一些问题,各个浏览器的语法不统一,为了实现交互前端要频繁的进行dom操作,用原生js处理这些问题时颇为麻烦,业界急需一个统一的前端跨浏览器处理方案
jQuery应时而生
前文我们讲了,当时前端有两个痛点
- 缺乏一个完善的跨浏览器处理方案
- 缺乏简单的处理dom、事件操作的函数库
时势造英雄,为了解决这些问题,层出不穷的出现了许多函数库解决方案,在众多解决方案之中,一个名叫jQuery的函数库最为亮眼,解决了以上两个问题,还以简单的写法在全球前端中风靡。
依赖jQuery,前端写代码难度大大下降
举个例子:点击图片隐藏
<button id="button">点击我</button>
<img id="image" src="xxx.jpg">
原生代码:
var button = document.getElementById("button"),
image = document.getElementById("image")
button.onclick = function() {
image.style.display = "none";
};
jQuery:
$("button").onclick = function() {
$("image").style.display = "none";
};
现代框架
随着jQuery的流行,前端开发在网页开发中的话语权逐渐上升,前端自己维护页面状态,通过jQuery修改dom元素和事件完成交互,通过Ajax实现和后端的数据交流。前端开发向着好的流程前进。
但是在逐渐成熟之后,前端们发现目前的前端技术栈不太满足模块化、协作化的趋势,而且前端需要一个全面的方案来迎合新的开发思想:MVC、MVVM,这种数据和视图分离的思想有助于降低耦合性,增加复用性。同时,如何维护网页中越来越多的状态也成为了衡量前端开发能力的表现。
种种原因之下,前端开发进入下一个阶段水到渠成
在三大框架之前,在模板引擎的设计思想上得到了启发,前端也做了非常多的尝试比如
- Backbone.js
- Ember.js
- AngularJS
- Knockout.js
这些框架解决了部分问题,迎合了MVC模型,但是又不够好用。
最终,大家众所周知的三大框架站上了时代舞台
- React.js
- Angular
- Vue.js
也成为了现代前端开发的流行框架
未来的趋势
在以上技术的加持以及JavaScript技术更新之后,前端的能力越来越广,前端涉足的业务也越来越多
- 游戏开发(coco2d-js,白鹭)
- webApp开发(flutter RN weex ionic)
- 图形开发WebGl(three.js)
- 小程序/快应用
- 后端(nodejs)
- 桌面应用(electron)
- 嵌入式开发(Ruff)
- Web3开发(viem、wagmi)
而为了提高前端的开发体验,配套的开发配件也发展越来越快
- TypeScript 解决类型问题增加鲁棒性
- Webpack、Vite 解决了打包问题
- Next、Nuxt 提供了全面能力的前端框架
未来前端,或者说大前端会有更多想象空间
前面讲了很久,想必大家对过去二三十年前端的发展有了一个初步的认识,那么我们现在进入正题,了解下什么是React、以及React的优劣势,包括React在web3的应用
关于React
React是什么
React是Meta2013年推出的内部使用的前端框架,是目前使用人数最多的前端框架,拥有最好的社区支持和生态圈以及大量的第三方工具,在海外占比最高
React开发思想
我个人理解,React开发的思想是拆解、抽象、封装、组合
React团队推荐开发者把一个⼤需求拆解,使⽤ JSX 语法编写粒度小的可复用性的组件合成需要的用户页面,开发者只需要考虑如何拆解复用组件,管理每个组件的状态。而无需考虑整体的复杂情况
React解决了什么问题
在吹逼优点之前,我们不妨了解下React解决了哪些我们上面提到的的问题
- 使用虚拟DOM和声明式编程,解决了jQuery仍需要手动操作DOM的问题
- 通过单向数据流的思想简化了数据管理的复杂度,易于理解和维护
- 引入组件化开发模式,满足前端工程化和模块化的趋势
- 可以使用React Native来实现跨平台开发
React的优点
vs vanilla.js
-
引入组件化开发
拆解需求,把大化小,把一个页面拆解成一个个小组件,只需要维护每个小组件的功能,整个成一个页面。方便维护、开发、复用
-
使用声明式编程和虚拟DOM技术
代码编写简单,只需要描述你想要的 UI 结果,而不需要手动操作 DOM。这样可以使代码更加简洁、清晰、易于维护。⽆需考虑实际处理
在这个例子里,我们生成了一个组件,自动接收tasks数据直接会生成对应的代码,而不需要在内部详细描述接收props之后怎么处理生成对应的代码
import React from 'react'; interface Task { id: number; title: string; } interface TaskListProps { tasks: Task[]; } // 声明式编程:将任务列表数据作为 props 传递 const TaskList: React.FC<TaskListProps> = ({ tasks }) => ( <div> <h1>任务列表</h1> <ul> {tasks.map(task => ( <li key={task.id}>{task.title}</li> ))} </ul> </div> ); export default TaskList;
-
单向数据流
单向数据流的使用使得数据是可预测,可追溯的,容易管理状态
-
跨平台兼容性
可以用同样的语法使用React Native写移动端代码
-
灵活的状态管理机制
使用开源的Redux、Mobx、zustand等库更方便管理每个组件的状态
vs 其他框架
- 海外市场占比最高,岗位最多
-
TypeScript支持好
- 类型安全: TypeScript 是一种静态类型检查的语言,可以在编译时发现并修复许多常见的错误。在 React 项目中使用 TypeScript 可以帮助开发者捕获潜在的类型错误,减少在运行时出现的错误,提高代码的质量和稳定性。
- 智能提示: 在使用 TypeScript 编写 React 代码时,编辑器(如 Visual Studio Code)可以根据类型信息提供智能提示和自动补全的功能,使得开发过程更加高效和流畅。开发者可以更快速地找到需要的属性和方法,减少编码时的猜测和查找时间。
- 更好的代码维护性: TypeScript 的类型系统可以帮助开发者更好地理解和维护代码。类型定义可以作为代码的文档,帮助他人更快地理解代码的意图和用法。此外,当代码需要进行重构或修改时,类型检查器可以帮助开发者更容易地发现并修复相关的依赖项。
- 更好的团队协作: 使用 TypeScript 编写 React 代码可以提高团队之间的协作效率。类型定义可以作为接口的约定,使得不同团队成员之间的代码更容易理解和集成。此外,类型检查器可以在代码提交前进行静态检查,避免将错误的代码合并到主分支中。
-
符合函数式编程思想
React 的函数式编程范式使得组件更容易理解和测试,开发者可以使用纯函数和无状态组件来构建应用,从而使代码更加清晰和易于维护
-
JSX 书写代码更简单,写法更⾃由,逻辑更直观,不需要强制使用模板
- 易于理解和编写: JSX 结合了 JavaScript 和 HTML 的语法,使得开发者可以在 JavaScript 中直接编写类似 HTML 的代码,减少了学习新语法的成本,并使得代码更加易于理解和编写。
- 直观的语法: JSX 的语法结构类似于 HTML,使得开发者可以直观地描述 UI 结构和样式。这种直观的语法使得开发者可以更快速地理解代码的意图,并且可以更容易地与设计师和其他团队成员进行沟通和合作。
React的缺点
- JSX太灵活,由于JSX 写法太过⾃由导致缺乏范式和最佳实践,学习曲线陡峭,学会react不难,但是写好代码很难
- 社区生态相对分散,react官方没有提供一套官方完整可⽤的套件,如 Vue-Router、 Vuex,必须引⼊社区的优秀包才能实现完全的router、状态管理等功能,需要花费更多的时间来评估和选择适合项目的第三方库和工具
- 简中学习材料不如 Vue 丰富,质量略差
React趣谈
仅个⼈看法:
由于 Meta裁员和员⼯跳槽,不少之前的核⼼ React 开发⼈员都跳槽到了 Vercel,现在官⽹已宣布停⽌维护 create react app,⽽且官⽹建议启动新 React项⽬时直接使⽤ Next.js 、 Remix 等框架,这些框架在 React 基础上增加了全⽅位的功能,⽐如⽀持 SSR 、 SSG ,⾃带 Router管理、 api 中间件控制等等功能。在使⽤时增加了学习成本,需要学习框架的新特性,新功能,新写法。相⽐下来,未来开发 React⽅向与 Vercel绑定过于密切,从⾯对⼴⼤开发转向⾯对开源组织,最新的特性很多都是 Next.js 转正,不得不引起一些槽点。
以上,我们简单了解了下React和vanilla.js以及其他框架对比下来的优势和劣势,相信对各位选型会有一些借鉴作用。接下来我们将具体了解下react的思想和特性,方便我们更了解react。
React的概念和特性
React学习了现代开发的很多优秀概念并且创造了很多自己的特性,接下来我们会把其中几个关键的概念简单讲解下,希望能帮助各位理解
⼋股时间
- 单向数据流
- 虚拟 DOM
- JSX 语法
- 声明式编程(函数式编程)
- React Hook
etc.
单向数据流
单向数据流是一种数据流动的模式,在这种模式下,数据在应用中的流动方向是单向的,通常是从父组件流向子组件,直接来说:只能通过 Actions修改 State,从⽽触发View更新,⽆法反向控制,⼦组件只能监听⽗组件传来的 props修改⾃⼰的state,⽆法直接修改父组件的数据
如下图所示
单向数据流有很多优点
- 可预测性: 单向数据流使得数据的流向更加清晰和可预测。数据只能从父组件流向子组件,子组件无法直接修改父组件的数据。这种模式降低了数据流动的复杂性,使得代码更容易理解和调试。
- 可维护性: 单向数据流降低了组件之间的耦合性,使得组件更加独立和可复用。每个组件只关注自己的数据和逻辑,不需要关心其他组件的状态。这样可以提高代码的可维护性,减少了代码的藕合度,使得代码更易于扩展和修改。
- 数据流动更可控: 在单向数据流模式下,数据的流动是可控的。数据的变化只能通过 props 从父组件传递给子组件,子组件通过回调函数将事件传递给父组件。这种数据流动模式使得数据的变化更加可控,减少了意外的数据变化,提高了代码的健壮性。
- 易于调试: 单向数据流使得代码的数据流动更加直观和清晰,易于调试。开发者可以更容易地追踪数据的变化路径,找到错误的根源。此外,由于数据流动的方向明确,可以更轻松地定位和修复问题。
- 性能优化: 单向数据流模式可以更容易地实现性能优化。由于数据流动的方向明确,开发者可以更精确地控制组件的渲染时机,避免不必要的渲染和更新,提高页面的性能和响应速度。
虚拟DOM
前面我们讲了jQuery只是优化了操作DOM的难度,没有解决前端开发如果遇到复杂交互需要非常多手动操作DOM的问题,而react提出了自己的虚拟DOM方法来解决这个问题
从核心来看,React的虚拟DOM是一个Object树,它对应着实际 DOM 中的结构。当数据发生变化时,React 不会直接操作实际的 DOM 元素,而是通过react fiber方法比较新旧虚拟 DOM 树的差异(称为协调或调和),然后只对变化的部分进行实际的 DOM 操作。这种方式可以减少不必要的 DOM 操作,从而提高了页面的渲染性能。我们以实际例子来认识一下这个概念
// JSX的写法
const element = (
<div className='container'>
<h1>Hello, World!</h1>
</div>
);
// 等效React.createElement的写法
const elementReact = React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Hello, World!'),
);
// 实际存储的数据结构,React会用这个对象树来进行渲染
const virtualDOMTree = {
type: 'div',
props: {
className: 'container',
children: [
{
type: 'h1',
props: {
children: 'Hello, World!',
},
},
],
},
};
其实这是一种空间换时间的做法,减少DOM的操作次数,从而提升性能,
优点:
- 跟原⽣ DOM 操作⽐提⾼效率,每次更新只需要局部更新,不需要每次改动都触发重绘和回流
- ⽤空间换时间,在牺牲了内存开销和初次渲染的性能的前提下,提高了渲染效率
- 增加了可维护性,代码简约,开发者不用考虑实际DOM操作,降低前端开发的难度
JSX
JSX是一种 JavaScript 的语法扩展,常用于描述 React 组件的 UI 结构。JSX 看起来像是 HTML,但实际上是 JavaScript 的一种语法糖,它允许在 JavaScript 中直接书写类似 HTML 的标签,并最终编译调用。
import React from 'react';
function App() {
return <h1>Hello World</h1>;
}
React17前会在babel下转化成
import React from 'react';
function App() {
return React.createElement('h1', null, 'Hello world');
}
React17以后会转化成
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello world' });
}
最终再利用虚拟DOM树渲染成需要的HTML内容
Hook
说实话,React Hook是最难讲的一部分,因为React Hook改变了之前我们写类组件的惯性思想,但是确实是未来趋势,请各位朋友以后直接编写函数式组件取代类组件
React Hook 是 React 16.8 引入的一种新特性,它允许在函数组件中使用状态和其他 React 特性,而不需要编写类组件。Hook 提供了一组函数,使得在函数组件中能够实现以前只能在类组件中使用的功能,比如管理状态、生命周期方法、上下文等。
他避免了类组件写起来复杂,生命周期繁多,还强制super,this管理困难的问题,同时也符合函数式编程的思想:
1. 纯函数和无副作用
函数式编程强调使用纯函数,即相同的输入总是产生相同的输出,并且没有副作用。React 函数组件和自定义 Hook 遵循这一原则:
- 函数组件:React 函数组件通常是纯函数,根据输入的 props 生成相应的 UI。
- 自定义 Hook:自定义 Hook 也可以设计为纯函数,通过参数控制其行为。
2. 不可变性
函数式编程鼓励不可变数据结构,即不直接修改数据,而是返回新的数据副本。React Hooks 中的 useState
和 useReducer
都提倡这种模式:
- useState:状态更新通过 setter 函数进行,每次更新都会创建新的状态值,而不是直接修改原来的状态。
- useReducer:通过返回新的状态对象来更新状态,而不是直接修改现有状态。
3. 函数是一等公民
在函数式编程中,函数是一等公民,可以作为参数传递或作为返回值返回。React Hooks 提供了一种声明式的方式来使用函数,从而增强了代码的可组合性和重用性:
- useCallback 和 useMemo:这些 Hook 可以记忆化函数和计算结果,以优化性能。
- 自定义 Hook:可以将重复的逻辑提取到自定义 Hook 中,方便在不同组件中复用
React Hook的优点
- 简化代码结构:
- Hook 使得函数组件能够拥有状态和其他 React 特性,避免了类组件中复杂的 this 绑定问题和冗长的代码结构。
- 提高代码复用性:
- 自定义 Hook 可以将组件逻辑提取到可复用的函数中,方便在不同组件之间共享逻辑。
- 更好的逻辑组织:
- 使用 Hook,可以将与特定逻辑相关的代码组织在一起,而不是分散在不同的生命周期方法中,使得代码更易读、更易维护。
- 避免类组件的缺陷:
- 减少了类组件中的样板代码(boilerplate),如构造函数、生命周期方法等,使得代码更加简洁。
- 避免了 this 关键字的使用,使得代码更加直观和简洁。
- 轻量级和易测试:
- 函数组件通常比类组件更轻量级,更易于编写和测试。
- React 19会更新Hook的新特性,但是类组件没有同步更新
- useOptimistic
- useFormStatus
React Hook的缺点
- 学习曲线:
- 对于习惯了类组件的开发者,Hook 需要一些时间来学习和适应,特别是在理解 useEffect 和自定义 Hook 的用法时。
- 复杂度的提升:
- 在使用多个 Hook 时,特别是 useEffect,可能会导致代码变得复杂,难以调试和维护。
- 性能陷阱:
- 不当使用 useEffect、useCallback 和 useMemo 等 Hook 可能会导致性能问题,需要开发者仔细管理依赖数组和记忆化策略。
- 无法完全模拟React生命周期
- getSnapshotBeforeUpdate
- getDerivedStateFromError
- componentDidCatch
demo
说了那么多,我们用一个例子理解Hook
import { useState, useEffect, useCallback, useMemo } from 'react';
const Counter: React.FC = () => {
const [count, setCount] = useState(0);
const addCount = useCallback(() => {
setCount(count + 1);
}, [count]);
const text = useMemo(() => {
return `You clicked ${count} times`;
}, [count]);
useEffect(() => {
document.title = text;
return () => {
document.title = 'React App';
};
}, [text]); // 仅在 text 变化时重新运行副作用
return (
<div>
<p>{text}</p>
<button onClick={addCount}>Click me to add</button>
</div>
);
};
export default Counter;
在这个打点计数器里
useState
管理状态:初始化count
值为0,以及更新函数setCount
useEffect
触发副作用:- 使用
useEffect
动态更新document.title
为text
。 - 清理函数在组件卸载时和下次
useEffect
运行之前执行,恢复document.title
为React App
。
- 使用
useCallback
优化函数:优化addCount
函数缓存,防止多个状态影响组件重新渲染时都重新生成新的函数,(当然这个例子无用)useMemo
优化计算:使用useMemo
记住text
的计算结果,只有在count
变化时才重新计算。避免重复计算导致性能问题
以此大家应该对函数式组件和常用的React Hook 有了一些认识
以上我们把React中的一些主要的思想和特性交流了一番,其实React的技术核心不止于此,像中间提到的调和算法(fiber)也很值得深入学习,但是本次是以介绍为主,时间有限我们之后在聊
React 学习路径
-
官⽹react.dev/ :新官⽹以实践为主,一步步教你书写 React 组件代码,教你新特性,是最好的 React 教材
-
b站或者油管上各种免费讲解课程
-
购买对应付费课程
Code才是最好的学习⽅式,只看不敲代码,很难真的掌握语⾔,如有余⼒可以深⼊研究源码,甚⾄写一个 mini-react,更了解本质
React In Web3
React在Web3行业同样也有很广泛的应用,在交易所、Dapp、钱包中,都可以使用React配合Wagmi、viem来实现链交互、页面搭建等应用,而且海外React岗位远高于其他前端框架,学好React收益很大
转载自:https://juejin.cn/post/7371779128477925415