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一起创造更多的奇迹。

相关推荐
Mr_Xuhhh6 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋1 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿1 小时前
【前端】CSS
前端·css
ggdpzhk1 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•3 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜6 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点6 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow6 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o6 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic7 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端