React的发展历史和核心概念一览
前言
新框架的诞生必定有它的历史背景,不会无中生有。早在2011年,Facebook 开发人员面临代码维护方面的一些问题。随着Facebook Ads 应用程序的功能多样化,团队需要更多的人来保持它的稳定性。随着时间的推移,代码量急剧增加,维护性和扩展性只靠增加开发人员是解决不了实质问题。应用程序变得难以处理,如面临着大量的级联更新,他们的代码需要紧急升级以提高开发效率。
Facebook项目有独立的数据模型,但堆积了大量的用户交互代码。因此,Jordan Walke构建了一个框架原型,使开发流程更加高效,这标志着React.js的诞生。
History
-
2010 React的最初雏形
- Facebook将xhp引入其php堆栈并将其开源。
- Xhp基于xml语法提供可复合的组件, Facebook后来在React中引入了这种语法。
-
2011 React的原型
- Jordan Walke创建了FaxJS, 作为React试验阶段的产物,目前在github上还能看到。
TestProject.PersonDisplayer = { structure : function() { return Div({ classSet: { personDisplayerContainer: true}, titleDiv: Div({ classSet: { personNameTitle: true }, content: this.props.name }), nestedAgeDiv: Div({ content: 'Interests: ' + this.props.interests }), ageDiv: Div({ content: 'Age: ' + this.props.age }) }); } };
-
2012 面临挑战,开始新的尝试
- Facebook广告业务面临维护挑战,Jordan Walke基于FaxJS创建了React。
- 4月Instagram被Facebook收购,Instagram也希望采用React架构开发。由于React和业务耦合,Facebook面临将其开源的压力,当时大部分代码由Pete Hunt开发。
- 9月FaceBook CEO马克扎克伯格在TechCrunch会议上对外承诺将很快提供更好的移动体验。
-
2013 重大发布
- 在JS ConfUS会议上,Jordan Walke对外介绍了React,并将其开源。
- 6月React可以在JSFiddle上使用
- 7月React、JSX支持在Ruby上使用
- 8月React、JSX支持在Python上使用
- 9月Pete Hunt在JSConfEU 2013上发表了基于React重新思考最佳实践的演讲。
- David Nolen 介绍了基于React的OM。解释了 React如何优于现有的其他方案,从而提高了对React 的认可度。
-
2014 React快速扩展的一年,吸引更多的企业用户,如Netflix
- 年初的#reactjsworldtour会议上,React对外宣布开始建设社区,吸引更多的开发者
- 1月React给提供React Developer Tools,支持在Chrome上调试React应用
- 4月React London 2014会议召开,会议主题"如何构建响应式应用"
- ReactiveX.io诞生
- React Hot Loader发布,作为一个通用插件,支持React应用的热更新
-
2015 React脱颖而出
- 年初Flipboard发布React Canvas
- 1月Netflix开始使用React
- 2月Airbnb使用React
- 2月React.js Conf 2015会议,Facebook发布第一版React Native
- 3月Facebook宣布React Native (IOS)开源
- 6月Dan Abramov、Andrew Clark发布React状态管理库Redux
- React Native(Android)发布
-
2016 React成为主流框架
-
2017 稳中发展的一年
- 年初Airbnb发布新的React开源库Sketch.app
- 4月在F8会议上宣布React Fiber开源
- 9月React 16发布,包含errors boundries、portals、fragements、Fiber架构、SSR等。
- 11月React 16.2.0发布,改进Fragments
-
最近几年
- 2018年Iceland conference,发布React 16.3.0
- 2019年新的React DevTools发布
- 2020年发布17.0.0版本,支持React并发模式Concurrent Mode,处于试验阶段
- 2020年Dan Abramov等人提出Zero-Bundle-Size React Server Components
- 2022年3月React v18.0发布,提出并发渲染引擎( Concurrent Rendering Engine)、Server Side Components,通过可中断的渲染引擎提升用户交互体验
React优点
React基于JSX定义可重用组件,让开发变得更简单。另外,使用虚拟DOM来优化实际DOM的更新,从而加快渲染速度。以下是使用 React 的一些好处:
- 可重用组件:使用React,您可以构建可在应用程序的多个位置使用的可重用组件。随着时间的推移,这使得开发和维护您的应用程序变得更加容易。
- 快速渲染:React使用虚拟DOM(文档对象模型)来优化对实际DOM 的更新。这意味着当应用程序的状态发生变化时,React可以有效地更新DOM,从而加快渲染速度。
- JSX:React引入JSX概念,它是JavaScript的语法扩展,允许您在JavaScript文件中编写类似HTML的代码。这可以更轻松地构建和理解您的UI交互定义。
- 服务器端渲染:React可用于在服务器端渲染内容,为您的应用程序提供更快的初始加载时间,并提高您的应用程序对搜索引擎的性能。
- 强大的开发者社区:React拥有庞大而活跃的开发者社区,您可以在需要时找到丰富的资源和支持。
学习React之前你需要了解什么
Virtual DOM
文档对象模型(DOM)是HTML和XML文档的编程接口。它将文档的结构表示为对象树,每个对象代表文档的一部分(例如元素或属性)。DOM允许程序访问和修改文档的内容和结构。
Virtual DOM是React引入的概念,它是实际 DOM 的轻量级内存表示。当加载React应用程序时,虚拟DOM会创建DOM树的虚拟表示,用于确定需要对实际DOM 进行的最少更改数量,最后更新用户界面。
这种通过Virtual DOM更新DOM的过程称为reconciliation。当Virtual DOM确定需要对实际DOM 进行更改时,它会创建一个新的Virtual DOM来表示用户界面的更新状态,然后将新树与先前的树进行比较以确定最小更新集合。当React组件的状态发生变化时,将更新Virtual DOM而不是实际DOM,这可以极大地提高依赖频繁更新的具有大量数据的应用程序的性能。
JavaScript基础
- 基础的Javascript语法,包含变量、数组、对象、函数、闭包等概念。
- 异步:React包含大量的async代码,因此需要了解Promise、async/await。
- DOM文档对象模型:需要对DOM有清晰的了解,它是React更新UI交互的基础。
- ES6+: React使用ES6+ Javascript语法,因此你需要了解箭头函数、Class、快速分隔符(spread operator)等概念。
- 状态管理: 当开发大型应用时,通常需要基于组件式架构、状态管理(如Redux)来提升应用的开发效率、稳定性、扩展性。因此,也需要了解组价式架构、状态管理技术的理论知识。
React依赖的ES6特性
- Arrow functions: 新的函数定义语法,函数的间接表达并且可读性高。
- Classes:通过Javascript定义面向对象的类。
- Modules: 按模块组织和复用业务代码, 使用import、export导入、导出模块,可通过CommonJS、AMD等定义模块,然后使用Webpack、Babel打包成目标系统可执行的代码。
React提供了哪些能力
JSX
JSX(JavaScript XML)是JavaScript的扩展,允许开发人员在他们的代码中编写类似HTML的语法。JSX提供的主要好处是它能够更简单、更快地编写复杂的用户界面。通过允许开发人员在与React组件相同的文件中使用类似HTML的语法,快速生成UI交互。
一个简单的标题组件, JSX代码<h1>Hello, React!</h1>用于创建标题元素。这类似于用HTML编写<h1>Hello, React!</h1>。
import React from 'react';
function Heading() {
return <h1>Hello, React!</h1>;
}
export default Heading;
JSX元素也可以有属性,就像HTML元素一样。下面是一个使用自定义class属性呈现按钮的组件示例:
import React from 'react';
function CButton() {
return <button className="c-button">Click me</button>;
}
export default CButton;
我们在JSX中使用className而不是class,因为class是JavaScript中的保留关键字。您还可以使用JSX 来呈现动态内容。下面是一个组件示例,它呈现作为props传递的项目列表:
import React from 'react';
function ProjectList({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default ProjectList;
JSX必须先转译为JavaScript,然后才能在浏览器中运行。通常使用Babel工具来完成。使用此工具,您可以在React组件中编写JSX,并将其转换为浏览器可以理解的JavaScript。
State
状态是一个Javascript对象,它保存与React组件相关的数据和信息。可用于存储、管理和更新应用程序中的数据,进而允许对用户界面进行动态更改。例如,如果一个按钮需要根据用户输入更改其文本,那么这将通过使用输入的新值更新状态来完成。
组件还可以使用它们自己的状态以及其他元素的状态,以便在某些事件发生时显示相关信息或做出相应的响应。 状态可用于功能组件和类组件,下面是具有状态对象的类组件的示例:
import React, { Component } from 'react';
class StateComponent extends Component {
constructor(props){
super(props);
this.state = {
count: 0
};
}
render() {
return <h1>Count: {this.state.count}</h1>;
}
}
export default StateComponent;
在此示例中,组件有一个状态对象state,该对象具有一个名为count的属性,初始化为0。组件使用render()方法在标题中呈现计数。另外,可使用setState()方法更新组件的状态。
import React, { Component } from 'react';
class StateComponent extends Component {
constructor(props){
super(props);
this.state = {
count: 0
};
}
incrementCount = () => {
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div>
<h1>Count: {this.state.count}</h1>
<button onClick={this.incrementCount}>Increment</button>
</div>
);
}
}
export default StateComponent;
setState()是异步和批处理的,也就是说在短时间内多次调用setState()可以组合成一个更新。为避免任何潜在的错误,可使用setState()的回调函数以确保在执行任何其他逻辑之前状态已更新。
this.setState({ count: this.state.count + 1 }, () => {
console.log(this.state.count);
});
Props
Props是属性的缩写,将数据从父组件传递到子组件并作为每个元素行为的输入的方法。类似于函数参数,用于将数据从父组件传递到子组件。它们还可以用于在不同元素之间传递状态,以便跟踪用户与 UI交互期间所做的更改。
import ChildComponent from './ChildComponent';
function ParentComponent() {
return <ChildComponent name="React" />;
}
export default ParentComponent;
该示例中,父组件传递name属性给子组件,子组件接收name属性后可直接读取并在render函数中渲染至UI。
import React from 'react';
function ChildComponent({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default ChildComponent;
在ChildComponent中,可通过解构(destrcuting)方式获取局部属性。在父组件中,可通过快速分隔或属性键值对方式传递给子组件。
// 快速分隔符
const props = {name: "React", age: 25}
<ChildComponent {...props}/>
// 键值对形式
<ChildComponent name="React" age={25}/>
需要注意的是,props是只读的,在子组件中不能更改props值。如果要更新state,应使用useState自定义状态。
生命周期
生命周期是React开发很重要的概念,一共包含3个阶段,每个阶段有对应的函数或事件被用于触发组件特殊的行为。以下为生命周期的三个阶段:
- Mounting: 该阶段组件被初始化,render结果插入到DOM,生命周期方法constrcutor()、render()、DidMount()方法依次被调用。
- Updating:当状态发生变化或者有用户交互,都会触发Updating。在该阶段,shouldComponentUpdate、componentDidUpdate、render方法将会被触发。
- Unmounting:当组件从DOM上卸载,会触发Unmounting阶段,并且componentWillUnmount事件也会被执行。
生命周期过程包含的方法或事件:
- constructor(props):在挂载之前constructor会被触发,用于初始化组件状态和事件。
- componentDidMount(): 当组件被挂载和渲染之后触发,用于数据请求或服务订阅等副作用。
- shouldComponentUpdate(nextProps, nextState): 在组件更新之前触发,通过返回的false或true控制是否需要re-render。
- componentDidUpdate(prevProps, prevState): 当组件更新之后触发,用于执行获取新数据或更新服务等副作用。
- componentWillUnmount(): 在组件卸载之前调用,执行清理任务,如取消服务订阅等。
事件
- React提供的事件让开发人员可以很方便、高效地相应UI交互。
- React允许开发人员可直接在组件或DOM元素上绑定事件Handler, 事件通过on[event]语法绑定处理。
- React内置了常用的交互事件,如onMouseEnter、onChange、onFocus、onBlur、onKeyDown、onKeyUp、onSubmit等等
import React, { Component } from 'react';
class CButton extends Component {
handleClick = () => {
console. log('Button clicked');
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
export default CButton;
Refs
ref 是一种直接访问DOM元素或React组件的方法。Refs允许开发人员将名称或ID与任何给定组件相关联,使他们能够在以后需要时引用它。这在处理动态或不可预测的用户输入(例如文本输入字段)时特别有用。
在 React中有两种创建引用的方法:使用 createRef 方法或使用useRef Hook。以下是使用createRef方法创建ref的示例:
import React, { Component } from 'react';
class CComponent extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
handleClick = () => {
this.myRef.current.focus();
}
render() {
return (
<div>
<input type="text" ref={this.myRef} />
<button onClick={this.handleClick}>Focus</button>
</div>
);
}
}
export default CComponent;
在此示例中,ref在构造函数中创建,并分配给类的myRef属性。使用ref属性将ref传递给input元素。ref的当前属性用于访问底层DOM元素。handleClick方法将触发输入元素的焦点方法。
Keys
Key用于跟踪集合中的各个组件,以便可以轻松识别和有效地操作它们,而不会在元素之间造成不必要的重复或混淆。
class CList extends Component {
render() {
const items = [
{ id: 1, text: 'Item x' },
{ id: 2, text: 'Item y' },
{ id: 3, text: 'Item z' },
];
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
}
export default CList;
在此示例中,项目列表是使用map函数呈现的。每个项目都有一个用作键的唯一id属性,key应该在它的同级中唯一并且是稳定可预测的。不建议将数组index作为键,因为它们不是标识符,当元素添加到数组或从数组中删除时索引会发生变化。
Router
路由器允许开发人员创建SPA,这些应用程序可以轻松处理导航和 URL更改,无需重新加载整个页面或执行重定向。这有助于改善用户体验,允许它们在应用程序的不同部分之间无缝转换,同时仍然保持所有必要数据的完整性。
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from 'react-router-dom';
import Home from './Home';
import About from './About';
import Contact from './Contact';
export default function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/contact">
<Contact />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
上述示例中,BrowserRouter作为顶层组件被用作路由映射。Link组件用来创建连接,导航到不同的路由。Switch组件用来渲染匹配URL的第一个路由。Route组件定义路由项,将URL和组件绑定。
你可以使用useParams hook方法获取URL传递的参数:
import { useParams } from "react-router-dom";
function User() {
let { id } = useParams();
return <h3>ID: {id}</h3>;
}
function App() {
return (
<Router>
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/user/42">User 42</Link>
</nav>
<Switch>
<Route path="/user/:id">
<User />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
Flux
Flux是一个架构模式,用于管理React组件的单项数据流。它提供清晰的结构,将应用交互和UI交互层分离,使代码有比较清晰的结构,易于维护并且扩展性高。
Flux架构主要的组成部分为:
- Actions:被用户或系统触发的某些事件行为,如button click。
- Dispatcher:接收Actions并分发到对应的stores。
- Stores: 存储应用状态state, 接收dispatcher分发的Action,并更行对应的state。
- Views: 表示用户交互的React组件,它们接收stores的状态并渲染相应的视图。
Flux应用是单项数据里,这意味着数据流只会有一个方向:从action->store->view。
HOC
- Higher Order Components (HOCs) 作为更高级的React组件,被设计为封装其他组件,并提供额外的功能。
- HOC可以理解为可复用的代码片段,用于让开发人员快捷地提供诸如数据获取、状态管理、权限认证等通用功能,减少不必要的重复代码。
- HOCs为开发人员提供了一种方式,可以通过将通用逻辑抽象到单独的组件,来分离业务关注点,并且可以在整个应用重复使用这些通用的逻辑。分离业务关注点,这有助于保持代码的条理性、扩展性,降低由于缺乏维护而引入的错误风险。此外,由于HOC依赖于组合而不是继承,因此与传统的面向对象编程原则(如类和对象)相比,它们更容易让初学者掌握。
以下HOC示例添加新属性name到组件:
const withName = (WrappedComponent) => {
return class extends React.Component {
render() {
return <WrappedComponent name="John" {...this.props} />
}
}
}
class MyComponent extends React.Component {
render() {
return <div>My name is {this.props.name}</div>
}
}
const MyComponentWithName = withName(MyComponent)
withName高阶组件作为一个函数,接收类型为组件的参数,并返回一个新组件。新的组件附加了额外的prop属性name。
另一个HOC示例,用来按条件(用户角色)渲染组件:
const withRoleCheck = (WrappedComponent) => {
return class extends React.Component {
render() {
const { userRole, ...rest } = this.props
if (userRole === 'admin') {
return <WrappedComponent {...rest} />
} else {
return <div>You do not have permission to access this page</div>
}
}
}
}
class AdminPage extends React.Component {
render() {
return <div>Welcome to the admin page</div>
}
}
const AdminPageWithRoleCheck = withRoleCheck(AdminPage)
最佳实践
当开发React应用,保持好的实践可以确保应用代码清晰、可维护、便于开发。
-
编写组件的props和state尽量简单,Props、state仅用于提供组件渲染必要的数据,其他额外的数据应隔离在组件外,如单独的store。
-
组件render函数应尽量简单,render函数仅负责返回UI交互需要的JSX,其他额外的逻辑需要从render中移除。
-
组件应模块化、可复用,这意味着每一个组件应该仅负责的UI局部片段,并且每个组件在应用的任何位置都可以被复用。
还有一些附加的最佳实践点:保持组件业务聚焦和简单、使用函数式组件开发、持续更新React的版本、尽量为组件或元素提供key、使用lint作为编译调试工具。
总结
React从2011年被提出,从React的发展历史来了解它不失为一个好的方法,了解发展过程能够更加深入地体会到为什么会有后序的各种Hooks、状态管理、路由管理等等。
当我们在使用基于React实现的各种开源库时,了解清楚React框架本身提供的生命周期、路由、HOC等功能,才能够更合理、高效地使用这些开源库。
参考
写在最后,如果大家有疑问可直接留言,一起探讨!感兴趣的可以点一波关注。
转载自:https://juejin.cn/post/7242867910641139770