阅读视图

发现新文章,点击刷新页面。

JSX 基本语法与 React 组件化思想

JSX 基本语法与 React 组件化思想

在现代前端开发中,React 框架凭借其独特的 JSX 语法和组件化思想占据了重要地位。本文将结合实际代码示例,详细介绍 JSX 语法特性、组件化思想以及基本使用方法。

什么是 JSX?

JSX(JavaScript XML)是 React 中用于描述用户界面的语法扩展,它允许我们在 JavaScript 代码中直接编写类似 HTML 的标记,实现了 "在 JS 中写 HTML" 的开发体验。

// JSX语法示例
const element = <h2>JSX 是 React 中用于描述用户界面的语法扩展</h2>

这看似是 HTML,实则是 JavaScript 的语法糖。JSX 会被 Babel 等工具编译为普通的 JavaScript 函数调用:

// 编译后的JavaScript
const element2 = createElement('h2', null, 'JSX 是 React 中用于描述用户界面的语法扩展')

两者效果完全一致,但 JSX 的可读性和开发效率明显更高。

React 组件化思想

React 的核心思想之一是组件化,即将 UI 拆分为独立、可复用的部分,每个部分都可以单独维护。

组件的定义方式

在 React 中,组件可以通过函数来定义,返回 JSX 的函数就是一个组件:

// 函数组件定义示例
function JuejinHeader() {
  return (
    <div>
      <header>
        <h1>JueJin首页</h1>
      </header>
    </div>
  )
}

// 箭头函数形式的组件
const Ariticles = () => {
  return (
    <div>
      Articles
    </div>
  )
}

组件组合与嵌套

组件可以像搭积木一样组合使用,形成组件树:

function App() {
  return (
    <div>
      {/* 头部组件 */}
      <JuejinHeader />
      <main>
        {/* 文章列表组件 */}
        <Ariticles />
        <aside>
          {/* 侧边栏组件 */}
          <Checkin />
          <TopArticles />
        </aside>
      </main>
    </div>
  )
}

这种组合方式让我们可以将复杂页面拆分为多个简单组件,提高代码的可维护性和复用性。

JSX 基本语法规则

  1. 表达式插入:使用{}在 JSX 中插入 JavaScript 表达式
// 数据绑定示例
const [name, setName] = useState("vue");

// 在JSX中使用表达式
return (
  <h1>Hello <span className="title">{name}!</span></h1>
)
  1. 条件渲染:通过逻辑与运算符或三元表达式实现
// 条件渲染示例
{isLoggedIn ? <div>已登录</div> : <div>未登录</div>}

// 按钮文本根据状态变化
<button onClick={toggleLogin}>
  {isLoggedIn ? "退出登录" : "登录"}
</button>
  1. 列表渲染:使用数组的 map 方法渲染列表,需提供唯一 key
// 列表渲染示例
{todos.length > 0 ? (
  <ul>
    {todos.map((todo) => (
      <li key={todo.id}>
        {todo.title}
      </li>
    ))}
  </ul>
) : (
  <div>暂无待办事项</div>
)}
  1. 样式处理:class 属性需使用 className(因为 class 是 JavaScript 关键字)
// CSS类名使用className
<span className="title">{name}!</span>
/* 对应的CSS样式 */
.title{
  color: red;
}
  1. 根元素限制:JSX 最外层必须有一个根元素,或使用片段<>
// 使用片段避免多余的div嵌套
return (
  <>  
    <h1>标题</h1>
    <p>内容</p>
  </>
)
  1. 事件处理:使用驼峰命名法,如 onClick
// 事件处理示例
const toggleLogin = () => {
 setIsLoggedIn(!isLoggedIn);
}

<button onClick={toggleLogin}>切换登录状态</button>

响应式数据与 JSX

React 通过 useState 钩子实现响应式数据,当状态变化时,JSX 会自动重新渲染:

// 响应式数据示例
const [name, setName] = useState("vue");

// 3秒后更新状态,视图会自动更新
setTimeout(() => {
  setName("react");
}, 3000);

当 name 状态从 "vue" 变为 "react" 时,使用{name}的地方会自动更新,无需手动操作 DOM。

总结

JSX 和组件化是 React 的两大核心特性:

  • JSX 提供了一种直观、高效的方式描述 UI,将 HTML 和 JavaScript 无缝结合
  • 组件化思想将 UI 拆分为独立可复用的单元,使复杂应用的开发和维护变得简单
  • 通过状态管理实现响应式更新,让开发者专注于数据逻辑而非 DOM 操作

这种开发模式使得 React 在构建大型应用时具有明显优势,也是现代前端开发的重要思想。

Vue 任务清单开发:数据驱动 vs 传统 DOM 操作

Vue 任务清单开发:数据驱动 vs 传统 DOM 操作

在前端开发中,任务清单是一个常见的案例,通过这个案例我们可以清晰对比传统 DOM 操作与 Vue 数据驱动开发的差异。本文将结合具体代码,解析 Vue 的核心思想和常用 API。

传统开发方式的局限

传统 JavaScript 开发中,我们需要手动操作 DOM 元素来实现功能。以下代码为例:

<h2 id="app"></h2>
<input type="text" id="todo-input">
<script>
    // 传统方式需要先获取DOM元素
    const app = document.getElementById('app');
    const todoInput = document.getElementById('todo-input');
    
    // 手动绑定事件并操作DOM
    todoInput.addEventListener('change',function(event) {
        const todo = event.target.value.trim();
        if(!todo){
            console.log('请输入任务');
            return ;
        }else{
            // 直接修改DOM内容
            app.innerHTML = todo;
        }
    })
</script>

这种方式的特点是:

  • 需要手动获取 DOM 元素
  • 命令式地操作 DOM 进行更新
  • 业务逻辑与 DOM 操作混杂
  • 随着功能复杂,代码会变得难以维护

Vue 的数据驱动开发理念

Vue 采用了完全不同的思路:开发者只需关注数据本身,而非 DOM 操作。以任务清单为例:

<template>
  <div>
    <!-- 数据绑定 -->
    <h2>{{ title }}</h2>
    <!-- 双向数据绑定 -->
    <input type="text" v-model="title" @keydown.enter="addTodo">
    
    <!-- 条件渲染 -->
    <ul v-if="todos.length">
      <!-- 循环渲染 -->
      <li v-for="todo in todos" :key="todo.id">
        <input type="checkbox" v-model="todo.done">
        <span :class="{done: todo.done}">{{ todo.title }}</span>
      </li>
    </ul>
    <div v-else>暂无计划</div>
  </div>
</template>

Vue 的核心思想是:不再关心页面元素如何操作,只关注数据如何变化。当数据发生改变时,Vue 会自动更新 DOM,开发者无需手动操作。

Vue 常用 API 解析

  1. v-model 双向数据绑定

    <input type="text" v-model="title">
    

    实现表单输入与数据的双向绑定,输入框的变化会自动更新数据,数据的变化也会自动反映到输入框。

  2. v-for 循环渲染

    <li v-for="todo in todos" :key="todo.id">
    

    基于数组渲染列表,:key用于标识每个元素的唯一性,提高渲染性能。

  3. v-if/v-else 条件渲染

    <ul v-if="todos.length">
      ...
    </ul>
    <div v-else>暂无计划</div>
    

    根据条件动态渲染不同的内容,当todos数组为空时显示 "暂无计划"。

  4. :class 动态类绑定

    <span :class="{done: todo.done}">{{ todo.title }}</span>
    

    todo.donetrue时,自动为元素添加done类,实现完成状态的样式变化。

  5. @事件监听

    <input type="text" @keydown.enter="addTodo">
    

    监听键盘回车事件,触发addTodo方法,@v-bind:的缩写。

  6. computed 计算属性

    // 计算未完成的任务数量
    const active = computed(() => {
      return todos.value.filter(todo => !todo.done).length
    })
    
    // 全选功能的实现
    const allDone = computed({
      get(){
        return todos.value.every(todo => todo.done)
      },
      set(val){
        todos.value.forEach(todo => todo.done = val)
      }
    })
    

    计算属性具有缓存特性,只有依赖的数据变化时才会重新计算,相比方法调用更节省性能。全选功能展示了计算属性的高级用法,通过getset实现双向绑定。

  7. ref 响应式数据

    import { ref } from 'vue'
    const title = ref("");
    const todos = ref([...])
    

    创建响应式数据,当这些数据变化时,Vue 会自动更新相关的 DOM。

总结

Vue 通过数据驱动的方式,极大简化了前端开发流程:

  • 开发者可以专注于业务逻辑和数据处理
  • 减少了大量手动 DOM 操作的代码
  • 提供了简洁直观的 API,降低学习成本
  • 内置的性能优化(如计算属性缓存)让应用运行更高效

从后端模板到响应式驱动:界面开发的演进之路

从后端模板到响应式驱动:界面开发的演进之路

在 Web 开发的发展历程中,界面与数据的交互方式经历了多次重要变革。从早期的后端模板渲染到如今的响应式数据驱动,每一次演进都深刻影响着开发模式和用户体验。

一、纯后端套模板时代:MVC 模式的兴起

早期的 Web 开发普遍采用 MVC(Model-View-Controller)开发模式,这种模式将应用程序分为三个核心部分:

  • Model(模型) :负责数据管理,通常与数据库交互例如通过 MySQL 等数据库进行数据抽象存储
  • View(视图) :负责数据展示,以 HTML 模板为载体
  • Controller(控制器) :处理业务逻辑,协调模型和视图

这一时期的后端代码通常直接处理 HTTP 请求并渲染模板,典型的 Node.js 示例如下:

// 简化的后端MVC示例
const http = require('http');
const mysql = require('mysql');

// 模型:数据库连接
const db = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '',
  database: 'todoapp'
});

// 控制器:处理业务逻辑
const getTodos = (req, res) => {
  db.query('SELECT * FROM todos', (err, results) => {
    if (err) throw err;
    // 渲染视图(模板)
    const html = `
      
        
          <ul>
            ${results.map(todo => `<li>${todo.content}</li>`).join('')}
          </ul>
        
      
    `;
    res.end(html);
  });
};

// HTTP服务
http.createServer((req, res) => {
  if (req.url === '/todos') {
    getTodos(req, res);
  } else {
    res.end('Hello World');
  }
}).listen(3000);

在这种模式下,HTML 的静态部分和动态数据部分混合在一起,动态内容通过模板语法(如{{todos}})由后端数据驱动生成。

二、前后端分离:开发职责的解耦

随着 Web 应用复杂度提升,前后端分离架构逐渐成为主流。这种模式将应用拆分为两个独立部分:

  • 前端:专注于 HTML、CSS 和 JavaScript 实现,通过 Ajax 或 Fetch 主动从后端拉取数据
// 前端获取数据示例
fetch('http://localhost:3000/users')
.then(response => response.json())
.then(users => {
  // 处理并展示数据
  const userList = document.getElementById('user-list');
  userList.innerHTML = users.map(user => `<li>${user.name}</li>`).join('');
});

后端:不再返回完整 HTML,而是提供纯粹的数据接口(API)

// 后端API示例
http.createServer((req, res) => {
  if (req.url === '/users') {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify([
      { id: 1, name: '张三' },
      { id: 2, name: '李四' }
    ]));
  }
}).listen(3000);

前后端分离带来了显著优势:

  • 前端开发者可专注于数据展示和用户体验
  • 后端开发者可专注于数据处理和系统性能
  • 双方可并行开发,通过 API 契约协作

三、Vue 响应式:数据驱动的革命

Vue 框架的出现彻底改变了前端开发模式,其核心是响应式数据驱动

1. 响应式数据(ref 实现)

Vue 通过ref将普通数据包装为响应式对象:

import { ref } from 'vue';

// 创建响应式数据
const message = ref('Hello Vue');
const todos = ref([
  { id: 1, content: '学习响应式' },
  { id: 2, content: '使用v-for' }
]);

// 修改数据(自动触发界面更新)
message.value = 'Hello Reactive';
todos.value.push({ id: 3, content: '掌握Vue' });

2. 模板语法(数据与界面绑定)

Vue 模板通过{{}}和指令实现数据与界面的自动关联:


  <div>
    
    <p>{{ message }}</p>
    
    
    <ul>
      <li>
        {{ todo.content }}
      </li>
    </ul>
  </div>

3. 完整 Vue 组件示例


  <div>
    <h2>{{ title }}</h2>
    <ul>
      <li>{{ item.name }}</li>
    </ul>
    添加项
  </div>



import { ref } from 'vue';

// 响应式数据
const title = ref('Vue响应式示例');
const list = ref([
  { id: 1, name: '第一项' },
  { id: 2, name: '第二项' }
]);

// 业务逻辑(只操作数据)
const addItem = () => {
  list.value.push({
    id: Date.now(),
    name: `新项${list.value.length + 1}`
  });
};

在 Vue 的响应式体系中,开发者只需关注数据变化,界面更新完全由框架自动处理。这种模式继承了后端模板 "数据驱动界面" 的思想,但通过前端响应式系统实现了更高效、更灵活的开发体验,彻底摆脱了手动 DOM 操作的困扰。

从后端模板到 Vue 响应式,界面开发的演进始终围绕一个核心:让开发者聚焦业务逻辑,而非界面更新细节。Vue 的响应式系统正是这一理念的完美实践。

JavaScript 内存机制与闭包解析

JavaScript 内存机制与闭包解析

JavaScript 作为一门动态弱类型语言,其内存管理机制和闭包特性是理解 JS 运行原理的核心。本文将结合具体代码示例,深入解析 JS 的内存机制以及闭包的工作原理。

一、JavaScript 内存空间划分

JavaScript 的内存空间主要分为三大类:

  1. 代码空间:用于存储执行的代码
  2. 栈内存:主要存储简单数据类型和引用类型的地址
  3. 堆内存:主要存储复杂数据类型(对象等)

2bad8a6ec6a17f7a384612ea3e0cbdcd.png 栈内存的特点是操作速度快、大小固定、存储连续,适合存储体积小的数据;而堆内存空间大,可存储大型复杂对象,但分配和回收内存相对耗时。

二、数据类型的内存存储

1. 简单数据类型的存储

简单数据类型(如数字、字符串、布尔值等)直接存储在栈内存中,赋值时会进行值拷贝:

// 调用栈在栈内存中
// 体积小
function foo() {
    var a = 1; // 赋值,直接存储在栈内存
    var b = a; // 拷贝,在栈内存中创建新值
    a = 2;
    console.log(a); // 2
    console.log(b); // 1
}
foo();

上述代码中,ab是相互独立的变量,修改a的值不会影响b,因为它们在栈内存中占据不同的空间。

2. 复杂数据类型的存储

复杂数据类型(如对象)在栈内存中存储的是指向堆内存的地址,赋值时进行的是引用拷贝:

function foo() {
    var a = {name:"极客时间"}; // 栈内存存储地址,堆内存存储对象
    var b = a; // 引用式拷贝,ba指向堆中同一个对象
    a.name = "极客邦";
    console.log(a); // {name: "极客邦"}
    console.log(b); // {name: "极客邦"}
}
foo();

这里ab在栈内存中存储的是同一个地址,指向堆内存中的同一个对象,因此修改a的属性会影响b

三、JavaScript 的动态弱类型特性

JS 作为动态弱类型语言,变量的类型可以在运行过程中动态改变,不需要预先声明类型:

// JS 是动态弱类型语言
var bar; 
console.log(typeof bar); // undefined
bar = 12; // 变为number类型
console.log(typeof bar); // number
bar = '极客时间'; // 变为string类型
console.log(typeof bar); // string
bar = true; // 变为boolean类型
console.log(typeof bar); // boolean
bar = null;
console.log(typeof bar);  // Object (JS设计的bug)
bar = {name:'极客时间'}  // 变为Object类型
console.log(typeof bar); // Object

四、执行上下文与调用栈

JavaScript 引擎通过调用栈来管理执行上下文,每个函数执行时都会创建一个执行上下文,包含:

  • 变量环境
  • 词法环境
  • outer(词法作用域链)
  • this

lQLPJw1WF0yif-fNAhTNBHawwsioxubOAzAJEmTtM6BTAA_1142_532.png 执行上下文的切换通过调整调用栈的栈顶指针来实现,这也是栈内存需要高效操作的原因。

五、闭包的内存机制

闭包是指内部函数可以访问外部函数作用域中变量的特性,其实现依赖于特殊的内存管理机制。

闭包示例解析

function foo() {
    var myName = "极客时间"
    let test1 = 1
    const test2 = 2
    var innerBar = { 
        setName:function(newName){
            myName = newName
        },
        getName:function(){
            console.log(test1)
            return myName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName() // 输出1
console.log(bar.getName()) // 输出1和"极客邦"

闭包的内存工作原理

  1. 编译阶段:JS 引擎编译foo函数时,会扫描内部函数(setNamegetName
  2. 识别闭包:发现内部函数引用了外部函数的变量(myNametest1),判断形成闭包
  3. 创建闭包对象:在堆内存中创建一个closure(foo)对象,用于保存被内部函数引用的外部变量
  4. 维持引用:当foo函数执行完毕后,虽然其执行上下文出栈,但由于bar仍引用着内部函数,而内部函数又引用着closure(foo),所以这些变量不会被回收
  5. 访问变量:内部函数通过引用closure(foo)来访问外部函数的变量

lQLPKGXzE8qWf4fNAjTNBHawul-TTHmpmmMJEm9r57TIAA_1142_564.png

六、内存管理的特点

  • JavaScript 不需要手动管理内存(不同于 C/C++ 需要使用mallocfree
  • 栈内存的回收通过调整栈指针实现,效率高
  • 堆内存的回收通过垃圾回收机制实现,当对象没有任何引用时会被回收
  • 闭包会延长变量的生命周期,可能导致内存占用增加,需要合理使用
❌