"『React新欢指南』:第一次总是那么Reactant"

5,608 阅读11分钟

段子

在编程界的浪漫都市里,每一位开发者都在寻找那个让他们心动的“新欢”。故事的主角,一位对代码充满热情的程序员小白,偶遇了名叫React的迷人框架。他们的第一次邂逅,是在一个阳光明媚的午后,命令行轻轻吐出“create-react-app my-first-love”。

小白的心跳随着npm安装的进度条起伏,紧张又兴奋。当“localhost:5173”在浏览器上悄然绽放,那一刻,小白知道,自己已经深深陷入了React的温柔怀抱。Reactant,这个化学般的术语,在这里变成了爱情的催化剂,让每一次组件的组合都如同初恋般新鲜且充满魔力。

“Hello, World!”,这简单的问候语,却是他们爱情宣言的开始。React用它的虚拟DOM承诺,让每一个UI交互都如丝般顺滑,没有多余的DOM操作,只有彼此间默契的更新与渲染。

小白开始沉迷于构建一个个精美的组件,就像为React准备的小礼物,每一个都承载着对完美用户体验的追求。他们一起经历了状态管理的困惑,战胜了路由配置的挑战,而React社区,就像双方共同的朋友圈,总能在关键时刻伸出援手。

就这样,小白在React的世界里越走越深,每一次的“setState”不仅是状态的改变,更是他们关系的一次升华。在这个充满Reactant的旅程中,小白学会了不只是技术,更是如何用心去感受每一次代码的呼吸,每一次应用的跳动。

于是,“第一次总是那么Reactant”不再是一句玩笑,而是他们之间不解的情缘。在这个由组件构成的爱情故事里,React不仅仅是前端的王者,更是小白心中永恒的“新欢”。

废话不多说我们直接开始接下来的react之旅

环境搭建

要开始React之旅,首先得搭建好开发环境。幸运的是,Facebook为我们准备了create-react-app这一官方脚手架,大大降低了React项目的初始化门槛。

  1. 安装Node.js与npm:React开发离不开Node.js环境,确保你已经安装了Node.js,npm(Node包管理器)也随之而来。

  2. 全局安装create-react-app:通过命令行运行npm install -g create-react-app,这个命令会在全局范围内安装create-react-app。使用npm config ls查看全局安装路径。

  3. 创建新项目:执行create-react-app my-react,这一步会基于React的最佳实践为你生成一个全新的项目骨架,包括开发目录结构、基础配置文件以及必要的依赖包。

React应用快速搭建与渲染机制详解:从create-react-app到Virtual DOM

create-react-app 是一个官方提供的脚手架工具,它简化了创建和初始化React应用程序的过程。当你运行这个命令后,它实际上做了很多事情来快速搭建一个可以立即开始开发的React项目环境。以下是根据您列出的步骤,对React应用是如何被渲染的进行详细说明:

初始化过程

Git Clone模板项目:虽然create-react-app本身并不直接执行git clone操作,但这个步骤可以理解为它内部从其模板仓库获取基础项目结构和配置文件,类似于克隆了一个预设的项目模板到本地。实际上,它使用内部的模板和脚本创建项目结构,无需手动克隆。

探索项目结构

  • src目录是你的主要工作区,存放所有源代码。index.js作为项目的入口文件,index.css则是全局样式文件。

  • node_modules包含了项目的所有依赖包,package.json记录了项目的元数据和依赖关系。

  • public目录存放静态资源,index.html是应用的入口页面,其中的div#root是React应用的挂载点。

渲染流程

  • Webpack打包:当项目启动或构建时,Webpack会将src/目录下的所有JavaScript代码(包括React组件)进行编译、压缩,并且处理模块依赖,最终输出到一个或多个bundle文件中,通常位于/public/js/目录下。

  • React渲染过程

    • ReactDOM.render():React应用的根节点通常在src/index.js或类似入口文件中通过ReactDOM.render()方法启动。这个方法接收两个参数:要渲染的React元素(通常是App组件)和DOM中的一个容器元素(如<div id="root">),然后React会接管这个容器,将其内容替换为React组件渲染的结果。

    • 虚拟DOM:React使用虚拟DOM(Virtual DOM)技术来提高渲染效率。在内存中,React维护一个组件树的轻量级表示,当应用状态改变时,React会比较新旧虚拟DOM的差异,并计算出最小化的DOM操作集,最后将这些变化应用到实际DOM上,这一过程称为"reconciliation"(协调)。

启动开发服务器

运行cd my-react进入项目目录,接着执行npm run start,React就运行起来了。一个本地开发服务器会在3000端口启动,访问localhost:3000,你就能看到React应用的初始页面。

create-react-app背后做了哪些事?它简化了一系列复杂的配置步骤,为你搭建好了开发环境:克隆预设的模板项目、安装必要的依赖、配置了开发服务器等。这一切,只为让你能快速投入到核心开发工作中。

了解完react的基础和渲染逻辑,我们来写一个小demo来体会一下react的独到之处 首先,进行初始化操作

image.png

下载完后我们先来了解一下src目录下的文件以及代码是在哪里运行的

项目实践

src目录结构与代码运行简介

在React应用中,src(source code的缩写)目录是放置所有源代码的地方。对于一个使用create-react-app初始化的项目,src目录可能包含以下几个关键文件和目录:

  1. index.js: 这是应用的入口文件,负责启动整个React应用。它通过调用ReactDOM.render()方法将App组件挂载到HTML中的一个指定元素(通常是index.html里的<div id="root"></div>)。

  2. App.js: 这是主要的应用组件,通常包含应用的布局和顶级路由配置(如果使用了路由)。它可能包含其他子组件,并管理应用的顶层状态。

  3. 其他组件: 根据应用复杂度,可能有额外的组件文件或组件目录,每个组件都封装了一部分UI逻辑。

  4. index.css: 应用的全局样式文件,可以定义基础样式和全局变量。

  5. 服务端交互: 如果应用需要与后端服务通信,可能会有专门的文件或目录(如services/api/)存放Ajax请求逻辑。

代码运行流程

  • 当你运行npm startyarn start命令时,create-react-app内置的开发服务器(通常是webpack-dev-server)会启动。
  • 服务器会编译src目录下的源代码,包括转换JSX为JS、处理CSS和处理其他静态资源。
  • 编译完成后,生成的静态资源和打包后的JS文件会在内存中托管起来,浏览器访问时会加载这些资源。
  • 浏览器加载index.html,此文件中通过<script>标签引用了打包后的JS文件。
  • 入口JS文件(通常是main.*.js)执行,初始化React应用,调用ReactDOM.render()方法将App组件渲染到页面上指定的DOM元素中。
  • 之后,React应用开始监听状态变化,当状态改变时,React使用虚拟DOM算法高效地更新实际DOM,实现UI的动态更新。

在这里我写了一个简易代办事项列表

image.png

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

这段代码表示在root挂载点渲染了App组件,相当于在id为root的这个div里加上了App.js里的jsx代码 下面就是App.js里的jsx代码,那么jsx是什么呢:JSX(JavaScript XML)是一种语法糖,允许在JavaScript中混写HTML(或者更准确地说,是类似HTML的语法)。JSX使用类似HTML的标签来定义元素结构。例如,<div className="container">...</div>定义了一个拥有container类的<div>元素。

return (
    <div className="App">
      <h1>简易待办事项列表</h1>
      <div>
        <input 
          type="text" 
          placeholder="输入新任务" 
          value={newTodo} 
          onChange={(e) => setNewTodo(e.target.value)}
        />
        <button onClick={handleAddTodo}>添加</button>
      </div>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            <span style={{ textDecoration: todo.isDone ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => toggleTodoStatus(index)}>
              {todo.isDone ? '未完成' : '完成'}
            </button>
            <button onClick={() => handleDeleteTodo(index)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

因为在入口函数导入并渲染了App组件,所以我们可以直接在App.js上写代码

    import React, { useState } from 'react';
import './App.css';

function App() {
  // 使用useState管理任务列表和新的任务输入
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');

  // 添加新任务的处理函数
  const handleAddTodo = () => {
    if (newTodo.trim()) {
      setTodos([...todos, { text: newTodo, isDone: false }]);
      setNewTodo('');
    }
  };

  // 删除任务的处理函数
  const handleDeleteTodo = (index) => {
    const updatedTodos = [...todos];
    updatedTodos.splice(index, 1);
    setTodos(updatedTodos);
  };

  // 标记任务完成状态改变的处理函数
  const toggleTodoStatus = (index) => {
    const updatedTodos = todos.map((todo, i) => 
      i === index ? { ...todo, isDone: !todo.isDone } : todo
    );
    setTodos(updatedTodos);
  };

  return (
    <div className="App">
      <h1>简易待办事项列表</h1>
      <div>
        <input 
          type="text" 
          placeholder="输入新任务" 
          value={newTodo} 
          onChange={(e) => setNewTodo(e.target.value)}
        />
        <button onClick={handleAddTodo}>添加</button>
      </div>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            <span style={{ textDecoration: todo.isDone ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => toggleTodoStatus(index)}>
              {todo.isDone ? '未完成' : '完成'}
            </button>
            <button onClick={() => handleDeleteTodo(index)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

渲染效果: image.png

image.png

那么跟传统js代码相比,这段代码有什么优势呢

  1. 组件化思维实例

React鼓励将界面拆分成小的可重用组件。比如,上面的代码中,虽然已经很简洁,但如果我们想象有一个更复杂的待办事项项组件,我们可以这样设计:

// TodoItem.js
function TodoItem({ todo, index, toggleTodoStatus, handleDeleteTodo }) {
  return (
    <li key={index}>
      <span style={{ textDecoration: todo.isDone ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={() => toggleTodoStatus(index)}>
        {todo.isDone ? '未完成' : '完成'}
      </button>
      <button onClick={() => handleDeleteTodo(index)}>删除</button>
    </li>
  );
}

然后在App组件中引入并使用TodoItem

import TodoItem from './TodoItem';

// 在ul部分替换为
{todos.map((todo, index) => (
  <TodoItem
    key={index}
    todo={todo}
    index={index}
    toggleTodoStatus={toggleTodoStatus}
    handleDeleteTodo={handleDeleteTodo}
  />
))}

这里创建了一个独立的TodoItem组件,使得代码更加模块化和易于复用。

  1. 状态管理实例

useState Hook的使用让状态管理变得简单直观。比如,添加新任务的逻辑:

const handleAddTodo = () => {
  if (newTodo.trim()) {
    // 使用扩展运算符和当前todos数组创建一个新数组,然后添加新任务
    setTodos([...todos, { text: newTodo, isDone: false }]);
    // 清空输入框
    setNewTodo('');
  }
};

这个函数展示了如何在不直接修改原状态(todos)的情况下,通过setTodos触发状态更新,进而刷新UI。

  1. 事件处理实例

事件处理体现了React的单向数据流思想:

<input 
  type="text" 
  placeholder="输入新任务" 
  value={newTodo} 
  onChange={(e) => setNewTodo(e.target.value)}
/>

这里的onChange事件监听输入框的值变化,并通过setNewTodo更新newTodo状态,实现UI与状态的同步更新,无需直接操作DOM。

总结

通过创建一个简易待办事项列表的实践,我们深入体会了React及其jsx语法的威力,以及create-react-app带来的便捷开发体验。这段代码不仅展示了React的核心概念,如组件化、状态管理与事件处理,还彰显了jsx在编写声明式UI方面的优雅。

JSX的优势概览:

  1. 清晰的组件结构:jsx允许直接在JavaScript中描述UI结构,使得组件逻辑与表现形式紧密相连,同时保持代码的可读性和可维护性。

  2. 高效的状态管理:通过Hooks(如useState),React使状态逻辑集中且易于理解。状态更新自动触发组件重新渲染,确保UI与数据同步,而无需手动操作DOM。

  3. 灵活的事件处理:jsx内联事件处理器简化了对用户交互的响应逻辑,促进代码的整洁和模块化,同时保持了JavaScript的全部能力。

  4. 强大的组件复用:将UI分解为小组件,每个组件关注自身的状态和行为,既提高了代码的复用性,也便于复杂界面的组织和管理。

  5. 虚拟DOM与性能优化:React通过虚拟DOM比较技术,仅在数据变化时做最小化DOM操作,极大提升了UI更新的效率,特别是在大型应用中。

总之,jsx结合React框架,不仅简化了UI开发的复杂度,还促进了代码的组织和管理,使得开发者能够快速构建出功能丰富、交互流畅的前端应用。通过实践这个待办事项列表示例,我们见证了从概念到实现的全过程,理解了React应用开发的基本思路和jsx的实战价值。