React复习全攻略:重温旧知,收获新知

简介

大背景

  • 起源于 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的理念)
  • React
    • React推崇 不可变(Immutable),为 单向数据流
    • 由于数据不可变,所以React无需监听数据的变化,React只对setState之后会有重新渲染的流程
    • 当数据发生变化(setState之后),React默认是通过比较 引用的方式(Diff) 进行的,所以若是不优化,则会导致大量非必要渲染,从而影响性能(代码要求更高)

渲染层面

  • Vue
    • Vue可以很快的计算出虚拟DOM的差异,这由于它监听了每一个组件的依赖关系,不需要重新渲染整个组件树
  • React
    • React数据变化时,会将全部子组件重新渲染。但可 shouldComponentUpdate 这个生命周期函数进行控制。

Diff 算法

  • Vue
    • 同层比较新老:新的不存在旧的存在就删除,新的存在旧的不存在就创建
    • 基于Snabbdom库,使用双向链表,边对比,边更新DOM
  • React
    • 递归同层比较,标识差异点保存到Diff队列,从而得到patch树,再统一操作批量更新DOM

其他层面

  • 模板语法不同: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。

简易写法:

js 复制代码
const element = <h1>Hellow World!!!<h1/>

语法规范

  • JSX只能有一个根标签
  • 标签使用变量(JS表达式),用大括号{}包起来
  • 类名class,变className使用
  • 内联样式,使用键值对写法,例如style={{color:'#fff'}}
  • 标签首字母
    • 小写字母开头:html标签
    • 大写字母开头:JSX组件

简易例子

js 复制代码
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可嵌入变量和表达式(运算符、函数调用),绑定属性和方法

js 复制代码
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:与运算符&&:条件成立渲染后面的标签或组件。
js 复制代码
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函数 来遍历数组,如下面的代码所示:

js 复制代码
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语法
  • 构造器是可选的,用于初始化数据
js 复制代码
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替代
js 复制代码
// 定义一个函数组件  
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版本后的生命周期函数:

挂载时

挂载时,是指组件实例被创建,首次插入到页面的过程。主要涉及到四个方法:constructorrendercomponentDidMount,还有16.3新增的 getDerivedStateFromProps

以下是使用这些方法的示例:

js 复制代码
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会根据这些变化决定是否重新渲染组件,并调用相应的生命周期方法。

主要涉及这几个方法:getDerivedStateFromPropsshouldComponentUpdategetSnapshotBeforeUpdatecomponentDidUpdate

以下是一个例子,说明了React组件在更新时涉及的主要生命周期方法:

js 复制代码
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.countthis.state.count,如果它们不同,则组件会重新渲染。
  • getSnapshotBeforeUpdate 在DOM更新之前被调用,它返回一个对象,该对象在 componentDidUpdate 的第三个参数中可用。这里我们获取了滚动列表的滚动位置。
  • componentDidUpdate 在组件更新后被调用,并且接收上一个props、上一个state和getSnapshotBeforeUpdate 返回的snapshot作为参数。我们使用snapshot中的滚动位置来恢复滚动位置,同时比较 prevCountthis.state.count 来记录变化。

卸载时

卸载时阶段指的是组件从DOM中被完全移除时。

例如在类组件中,componentWillUnmount 是一个在组件卸载及销毁之前直接调用的生命周期方法。你可以在这个方法中执行任何必要的清理操作,例如取消网络请求、清除定时器、解除事件监听器等。

js 复制代码
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一起创造更多的奇迹。

相关推荐
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9152 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
阑梦清川2 小时前
在鱼皮的模拟面试里面学习有感
学习·面试·职场和发展
懒大王爱吃狼3 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍