探索 React:从 DOM 操作到组件化开发的演变

249 阅读7分钟

本文介绍了 React 的演变历程,从传统的 HTML + JS + CSS 开发方式到现代的 JSX 语法糖。详细讲解了如何通过 create-react-app 创建 React 工程,以及 React 的核心概念如组件、状态管理和属性传递。通过实战代码示例,帮助你快速掌握 React 的基本能力和开发流程,提升前端开发效率。

  1. 从传统前端开发到 React JSX 的演变

在 学习 react 之前先来分析一下 react 出现的历史背景。

在传统开发方式下, 前端是如何构建页面的。

在传统方式下, 前端页面是由 HTML + 原生 JS + CSS 构成。

  • JS 的本质就是一个浏览器脚本, 可以操作 DOM, 可以去增删改查 DOM元素和属性。
  • JS 是一种可以操作 DOM 的动态语言。

那么我们可不可以完全不写 HTML, 只通过 JS 来构建 DOM呢? 答案是可以的。

基于 JS 创建 DOM

有了这个想法, 那么怎么来实现呢? 下面通过伪代码来设计一下实现思路。

我期望我的的任意一个标签都可以通过 JS 函数来创建,封装一个方法来做这个事情。

createElement

function createElement(type,params,...children){
    let el
    // 根据 type 创建 dom 元素
    // 根据 params挂载元素属性, 根据不同的特征设置, 比如 setAttribute、addEventlistener 等等
    // 遍历 children 挂载元素
    return el
}

按照这样的逻辑

创建下面的 HTML 就可以用 JS 来实现

<div id="app">
    <h1></h1>
    <h2></h2>
    <h3></h3>
</div>
createElement("div",{id:"app"}
    createElement("h1"),
    createElement("h2"),
    createElement("h3"),
)

React 最早就是用这种形式创建 DOM, 但是太难用了。所以希望用按照 HTML 的形式来写, 但是还是 JS 的东西。

于是 React 去找了 Babel 团队, 写了 @babel/preset-react 编译器。于是将 JSX 可以转换成 createElement。

React.createElement 与上面的有什么区别?

本质上都是 JSX, 经历了 Babel 编译器进行编译出来 React.CreateElement。

JSX 是 CreateElement 的语法糖, 通过 Babel 编译成 JS 代码。

React 本身不支持 jsx, jsx 只是语法糖方便我们编写, 最终还是借助相关的 Babel preset 把 jsx 语法转换为 React.createElement(...) JS引擎才可以识别, Vue 也可以写 JSX, 借助相关的 babel 转换为 Vue 中的 Render函数。

前端在做什么?

Vue 简单交互流程

React 简单交互流程

React 灵活性

React JSX 的灵活性在于它将 HTML 结构和 JavaScript 逻辑无缝结合,使得开发者可以在同一语法中动态生成内容和管理状态,极大地提升了代码的可读性和可维护性。

{ arr.map(item=> <div>{item.xxx}</div>)}
  1. 创建 React 工程

在构建现代前端应用时,React 是一个强大的工具,它提供了组件化的开发方式和高效的更新机制。创建一个 React 工程是开始使用 React 开发应用的第一步。

使用 CRA 创建 React App

create-react-app 可以通过 npx(npm 5.2.0 及以上版本附带的工具)来创建新的 React 应用。npx 会自动下载并运行 create-react-app,而不需要全局安装它。

如果你更喜欢全局安装 create-react-app,可以使用以下命令:

npm install -g create-react-app

create-react-app my-app

npx create-react-app demo-app
npx create-react-app demo-app-ts --template typescript
pnpm run eject
# pnpm run eject # 会将代码怎么打包、启动的脚本都暴露出来。
  1. React 的基本能力

每一个 React 文件都是一个组件, 有组件就有子父组件。

  1. 子父组件

组件又有函数组件和类组件。

webstorm 创建模板小技巧: rsf 函数组件、rcc类组件

  • 从界面的视角看

    • class 组件渲染的是 render 函数中的 return 的内容。
    • function 组件渲染的是函数本身返回的内容。
  1. State

在 react 中有一个概念叫 state

  • 如果我有一个数据, 我需要数据改变的时候,去触发界面更新。

    • 我要把这个数据定义为 state
    • 使用特定的方法去更新 state
  1. 类组件的 State

setState: 类组件要使用 setState 方法来管理状态。

  • state 的值互相不影响
  • 第二个参数是一个 callback, 能拿到更新后的 state
import React, { Component } from "react";

class ClassCom extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
      msg: "hello react",
    };
  }
  handleClick = (type) => {
    this.setState(
      {
        number: this.state.number + (type === "plus" ? 1 : -1),
      },
      () => {
        console.log(`number in callback: `, this.state.number);
      },
    );
    // 取不到最新的值,需要在 setState 的 cb 中获取
    // 原因 setState 有一个 isBatchedUpdate 要等所有任务执行完毕后再更新。
    console.log(`number in handleClick: `, this.state.number);
  };
  handleChange = (e) => {
    this.setState({
      msg: e.target.value,
    });
  };
  render() {
    const { number, msg } = this.state;
    return (
      <div>
        <h2>类组件 state 的用法</h2>
        <h3>{msg}</h3>
        <input type="text" value={msg} onChange={this.handleChange} />
        <p>this number is {number}</p>
        <button onClick={this.handleClick.bind(this, "plus")}>
          点击增加 number
        </button>
        <button onClick={() => this.handleClick("minus")}>
          点击减少 number
        </button>
      </div>
    );
  }
}

export default ClassCom;
类组件 setState 用法总结
  • constructor中使用 this.state = {} 声明状态
  • 通过 this.setState 修改状态的值两个参数,一个设置值,一个回调函数用来获取更新后的值
  • setState 有一个 isBatchedUpdate 要等所有任务执行完毕后再更新。
  • input 数据双向绑定: value + onChange
  • 注意 class 中的 this, 解决方案两种 bind 和 箭头函数。
  1. 函数组件的 State

useState Hook

函数签名 [state, dispatch] = useState(initState)

  • state 作为组件的状态, 提供给 UI 渲染视图。

  • dispatch 用户修改 state 的方法, 同时触发组件更新。

    • dispatch的参数可以是函数,可以不是。如果是函数就更新为函数执行的结果,否则直接更新为值。
  • initState 初始值, 可以是函数也可以是值。

import React, { useState } from 'react';

function Counter() {
  // 初始化 state 为 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>你点击了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>点击增加</button>
    </div>
  );
}

export default Counter;
  1. Props

子父组件传值的工具 类组件: props 传递

函数组件: props 传递

 : props 传递import ClassCom from "./src/basic/ClassCom";
import FuncCom from "./src/basic/FuncCom";
import { useState } from "react";

function App() {
  const [count, setCount] = useState(1024);
  const handleClick = () => {
    setCount((number) => ++number);
  };
  return (
    <div className="App">
      <ClassCom name={`我是类组件`} />
      <FuncCom name={`我是函数组件`} count={count} onClick={handleClick}>
        <h1>Hello Slot</h1>
      </FuncCom>
    </div>
  );
}

export default App;
import React, { Component } from "react";

class ClassCom extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
      msg: "hello react",
    };
  }
  handleClick = (type) => {
    this.setState(
      {
        number: this.state.number + (type === "plus" ? 1 : -1),
      },
      () => {
        console.log(`number in callback: `, this.state.number);
      },
    );
    // 取不到最新的值,需要在 setState 的 cb 中获取
    // 原因 setState 有一个 isBatchedUpdate 要等所有任务执行完毕后再更新。
    console.log(`number in handleClick: `, this.state.number);
  };
  handleChange = (e) => {
    this.setState({
      msg: e.target.value,
    });
  };
  render() {
    const { number, msg } = this.state;
    return (
      <div>
        <h2>{this.props.name}类组件 state 的用法</h2>
        <h3>{msg}</h3>
        <input type="text" value={msg} onChange={this.handleChange} />
        <p>this number is {number}</p>
        <button onClick={this.handleClick.bind(this, "plus")}>
          点击增加 number
        </button>
        <button onClick={() => this.handleClick("minus")}>
          点击减少 number
        </button>
      </div>
    );
  }
}

export default ClassCom;
import React, { useState } from "react";

function FuncCom({ name, count, onClick, children }) {
  const [number, setNumber] = useState(0);
  const [msg, setMsg] = useState("hello react");

  const handleChange = (event) => {
    setMsg(event.target.value);
  };
  const handleClick = (type) => {
    //setNumber(type === "plus" ? number + 1 : number - 1);
    //setNumber((prevState) => (type === "plus" ? prevState + 1 : prevState - 1));

    setNumber((number) => number + 1);
    console.log(number);
  };
  return (
    <div>
      <p>name:{name}</p>
      <p>
        count:{count} <button onClick={onClick}>修改count</button>
      </p>
      <h2>函数组件 state 的用法</h2>
      <p>{msg}</p>
      { /*非受控组件*/ }
      <input type="text" />
      { /*受控组件: 数据和state一一绑定 */ }
      <input type="text" value={msg} onChange={handleChange} />
      <p> this is number: {number}</p>
      <button onClick={() => handleClick("plus")}>plus number</button>
      <button onClick={() => handleClick("minus")}>minus number</button>
      {children}
    </div>
  );
}

function SubCom(props) {
  return <div>SubCom</div>;
}
export default FuncCom;
  1. 条件渲染和列表渲染

import React, { useState } from "react";

function RenderCom(props) {
  const [list, setList] = useState(["javascript", "ecmascript", "typescript"]);
  const [flag, setFlag] = useState(false);
  return (
    <div>
      {list.map((item) => (
        <div key={item.name}>{item.name}</div>
      ))}
      {flag ? <div>Hello</div> : <div>Bye Bye</div>}
    </div>
  );
}

export default RenderCom;

总结

  • React 的基本原理思想: 从 createElement 到 JSX
  • 脚手架创建 React 工程的方法
  • React 的基本能力: JSX 的用法、子父组件引用、State 的用法、Props 组件传递状态

本文深入探讨了 React 的演变和基本能力,从传统的 HTML + JS + CSS 开发方式,到使用 React 和 JSX 的现代化开发流程。首先,我们了解了如何通过 JavaScript 操作 DOM,并通过伪代码设计了 createElement 函数来动态创建 DOM 元素。接着,本文介绍了 React 的核心概念,包括组件、状态管理(state)、属性传递(props)以及条件和列表渲染。我们还展示了如何使用 create-react-app 快速创建和配置 React 项目。通过这些内容,你可以清晰地理解 React 的基本原理和实用技巧,掌握如何高效地构建和管理前端应用。