React 快速入门到精通教程:从零基础到能写项目

React 快速入门到精通教程:从零基础到能写项目

React 官方把它定义为:用 JavaScript 构建用户界面的库 ,核心思想是把页面拆成一个个组件,再用数据驱动页面变化。React 官方快速入门也强调,日常开发中最常用的能力包括组件、JSX、条件渲染、列表渲染、事件、状态和组件间数据共享。(React)


文章目录

一、React 是什么?

1. 专业解释

React 是一个用于构建用户界面的 JavaScript 库。它通过 组件化、声明式渲染、状态驱动 UI 的方式,让开发者可以更高效地构建复杂前端应用。

2. 大白话理解

React 就像"搭积木"。

一个页面不是一次性写成一大坨 HTML,而是拆成:

txt 复制代码
页面 = Header + Sidebar + Content + Footer

每一块都是组件,组件可以复用、组合、传数据。

3. 生活案例类比

做一套乐高房子:

txt 复制代码
门 = 一个组件
窗户 = 一个组件
屋顶 = 一个组件
房间 = 多个组件组合
整栋房子 = 整个 React 应用

以后你想换窗户,不需要推倒整栋房子,只改窗户组件。

4. React 适合解决什么问题?

React 适合:

  • 中后台管理系统
  • 电商网站
  • 社交应用
  • 数据可视化平台
  • 单页应用 SPA
  • 组件复用很多的业务系统

React 官方"Thinking in React"也推荐先把 UI 拆成组件层级,再确定每个组件的状态与数据流。(React)


二、React 核心基础

1. JSX

概念解释

JSX 是 JavaScript 的语法扩展,看起来像 HTML,但本质上会被编译成 JavaScript。

大白话

JSX 就是"在 JS 里写页面结构"。

代码示例

jsx 复制代码
function App() {
  const name = "小明";

  return (
    <div>
      <h1>Hello, {name}</h1>
      <p>欢迎学习 React</p>
    </div>
  );
}

export default App;

逐行解释

jsx 复制代码
function App() {

定义一个 React 函数组件。

jsx 复制代码
const name = "小明";

定义普通 JavaScript 变量。

jsx 复制代码
return (

组件返回 JSX。

jsx 复制代码
<h1>Hello, {name}</h1>

{} 在 JSX 中插入 JS 表达式。

jsx 复制代码
export default App;

导出组件。

常见错误

jsx 复制代码
return (
  <h1>标题</h1>
  <p>内容</p>
);

错误:JSX 必须有一个根节点。

正确:

jsx 复制代码
return (
  <>
    <h1>标题</h1>
    <p>内容</p>
  </>
);

面试追问

问:JSX 是 HTML 吗?

答:不是。JSX 是 JavaScript 的语法扩展,最终会被编译成 React 元素。


2. 组件

概念解释

组件是 React 应用的基本单位。React 应用就是由组件组成的树。

大白话

组件就是"页面零件"。

代码示例

jsx 复制代码
function UserCard() {
  return (
    <div>
      <h2>张三</h2>
      <p>前端工程师</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <UserCard />
      <UserCard />
    </div>
  );
}

export default App;

逐行解释

jsx 复制代码
function UserCard() {

定义用户卡片组件。

jsx 复制代码
<UserCard />

在 App 组件中使用 UserCard。

常见错误

组件名小写:

jsx 复制代码
function userCard() {}

React 会把小写标签当成原生 HTML 标签。

正确:

jsx 复制代码
function UserCard() {}

面试追问

问:React 为什么推荐组件化?

答:为了复用、拆分复杂度、降低维护成本。


3. Props

概念解释

Props 是父组件传给子组件的数据。

大白话

Props 就像"外卖订单备注":父组件告诉子组件该显示什么。

代码示例

jsx 复制代码
function UserCard(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>{props.job}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <UserCard name="张三" job="前端工程师" />
      <UserCard name="李四" job="后端工程师" />
    </div>
  );
}

export default App;

逐行解释

jsx 复制代码
function UserCard(props)

子组件接收 props。

jsx 复制代码
props.name

读取父组件传入的 name。

jsx 复制代码
<UserCard name="张三" job="前端工程师" />

父组件传值。

常见错误

直接修改 props:

jsx 复制代码
props.name = "王五";

错误。Props 是只读的。

面试追问

问:Props 和 State 有什么区别?

答:Props 是外部传入的,State 是组件自己管理的。


4. State

概念解释

State 是组件内部状态。状态变化后,React 会重新渲染页面。

React 官方文档说明,State 可以让组件"记住"用户输入、选择等信息。(react.nodejs.cn)

大白话

State 就是组件自己的"小记事本"。

代码示例

jsx 复制代码
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>当前数量:{count}</p>
      <button onClick={() => setCount(count + 1)}>加 1</button>
    </div>
  );
}

export default Counter;

逐行解释

jsx 复制代码
import { useState } from "react";

引入 useState。

jsx 复制代码
const [count, setCount] = useState(0);

定义状态 count,初始值是 0。

jsx 复制代码
setCount(count + 1)

更新状态。

常见错误

直接改 state:

jsx 复制代码
count++;

错误。页面不会可靠更新。

正确:

jsx 复制代码
setCount(count + 1);

面试追问

问:setState 是同步还是异步?

答:React 会批量处理状态更新,不应该依赖修改后的 state 立即同步可读。需要基于旧值更新时,用函数写法:

jsx 复制代码
setCount(prev => prev + 1);

5. 事件处理

代码示例

jsx 复制代码
function App() {
  function handleClick() {
    alert("按钮被点击了");
  }

  return <button onClick={handleClick}>点击我</button>;
}

export default App;

解释

jsx 复制代码
onClick={handleClick}

React 中事件名使用驼峰命名。

常见错误

jsx 复制代码
<button onClick={handleClick()}>点击</button>

这样会在页面渲染时立即执行。

正确:

jsx 复制代码
<button onClick={handleClick}>点击</button>

面试追问

问:React 事件和原生 DOM 事件有什么区别?

答:React 使用合成事件,提供跨浏览器一致的事件行为。


6. 条件渲染

jsx 复制代码
function App() {
  const isLogin = true;

  return (
    <div>
      {isLogin ? <h1>欢迎回来</h1> : <h1>请先登录</h1>}
    </div>
  );
}

export default App;

常见场景

  • 登录 / 未登录
  • 有数据 / 无数据
  • 加载中 / 加载完成

常见错误

jsx 复制代码
if (isLogin) {
  return <h1>欢迎</h1>;
}

这可以用,但不能直接在 JSX 里面写 if 语句。


7. 列表渲染

jsx 复制代码
function App() {
  const users = [
    { id: 1, name: "张三" },
    { id: 2, name: "李四" },
  ];

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default App;

重点

key 必须稳定、唯一。

常见错误

jsx 复制代码
<li key={index}>{user.name}</li>

如果列表会增删改,尽量不要用 index。

面试追问

问:key 的作用是什么?

答:帮助 React 判断哪些元素新增、删除、移动,从而提高更新效率并避免状态错乱。


8. 表单处理

jsx 复制代码
import { useState } from "react";

function LoginForm() {
  const [username, setUsername] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    alert(`提交用户名:${username}`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={username}
        onChange={e => setUsername(e.target.value)}
        placeholder="请输入用户名"
      />
      <button type="submit">提交</button>
    </form>
  );
}

export default LoginForm;

大白话

输入框的值交给 React 管,这叫受控组件

面试追问

问:受控组件和非受控组件区别?

答:受控组件由 state 控制表单值;非受控组件通过 DOM 或 ref 获取值。


三、Hooks 重点讲解

Hooks 是 React 函数组件中使用状态、副作用、引用、缓存等能力的方式。React 官方文档也强调,Hooks 可以组合使用,并可以封装成自定义 Hook。(react.nodejs.cn)


1. useState

jsx 复制代码
import { useState } from "react";

function LikeButton() {
  const [liked, setLiked] = useState(false);

  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? "已点赞" : "点赞"}
    </button>
  );
}

export default LikeButton;

场景

  • 弹窗开关
  • 表单输入
  • Tab 切换
  • 计数器

常见错误

jsx 复制代码
setCount(count + 1);
setCount(count + 1);

可能只加一次。

正确:

jsx 复制代码
setCount(prev => prev + 1);
setCount(prev => prev + 1);

面试追问

问:为什么 useState 返回数组?

答:方便开发者自定义变量名。


2. useEffect

概念解释

useEffect 用来处理副作用,比如请求接口、设置定时器、监听事件。

jsx 复制代码
import { useEffect, useState } from "react";

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []);

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default UserList;

逐行解释

jsx 复制代码
useEffect(() => {

组件渲染后执行副作用。

jsx 复制代码
fetch(...)

请求接口。

jsx 复制代码
}, []);

空依赖数组表示组件首次挂载后执行一次。

常见错误

忘记依赖项:

jsx 复制代码
useEffect(() => {
  console.log(keyword);
}, []);

如果 effect 依赖 keyword,应写:

jsx 复制代码
useEffect(() => {
  console.log(keyword);
}, [keyword]);

面试追问

问:useEffect 执行时机?

答:

txt 复制代码
无依赖:每次渲染后执行
空数组:首次挂载后执行一次
有依赖:依赖变化后执行
return 函数:组件卸载或下次 effect 执行前清理

3. useRef

jsx 复制代码
import { useRef } from "react";

function FocusInput() {
  const inputRef = useRef(null);

  function handleFocus() {
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} placeholder="请输入内容" />
      <button onClick={handleFocus}>聚焦输入框</button>
    </div>
  );
}

export default FocusInput;

场景

  • 获取 DOM
  • 保存不会触发渲染的数据
  • 定时器 ID

常见错误

以为 ref 改变会触发页面更新。不会。

面试追问

问:useRef 和 useState 区别?

答:useState 更新会触发渲染;useRef 更新不会触发渲染。


4. useMemo

jsx 复制代码
import { useMemo, useState } from "react";

function ProductList() {
  const [keyword, setKeyword] = useState("");

  const products = ["苹果", "香蕉", "橙子", "西瓜"];

  const filteredProducts = useMemo(() => {
    return products.filter(item => item.includes(keyword));
  }, [keyword]);

  return (
    <div>
      <input value={keyword} onChange={e => setKeyword(e.target.value)} />
      <ul>
        {filteredProducts.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;

大白话

useMemo 是"缓存计算结果"。

场景

  • 大列表过滤
  • 复杂计算
  • 避免重复计算

常见错误

滥用 useMemo。简单计算不需要缓存。


5. useCallback

jsx 复制代码
import { useCallback, useState } from "react";

function Child({ onClick }) {
  return <button onClick={onClick}>子组件按钮</button>;
}

function App() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log("点击子组件");
  }, []);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>加 1</button>
      <Child onClick={handleClick} />
    </div>
  );
}

export default App;

大白话

useCallback 是"缓存函数"。

面试追问

问:useMemo 和 useCallback 区别?

答:

txt 复制代码
useMemo 缓存计算结果
useCallback 缓存函数本身

6. 自定义 Hook

jsx 复制代码
import { useEffect, useState } from "react";

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    function handleResize() {
      setWidth(window.innerWidth);
    }

    window.addEventListener("resize", handleResize);

    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  return width;
}

function App() {
  const width = useWindowWidth();

  return <h1>当前窗口宽度:{width}</h1>;
}

export default App;

场景

  • 封装请求逻辑
  • 封装权限逻辑
  • 封装窗口监听
  • 封装表单逻辑

面试追问

问:自定义 Hook 为什么必须以 use 开头?

答:方便 React 识别 Hook 调用规则,也方便 ESLint 检查。


四、实战项目 1:TodoList

完整代码

jsx 复制代码
import { useState } from "react";

function TodoList() {
  const [text, setText] = useState("");
  const [todos, setTodos] = useState([]);

  function addTodo() {
    if (!text.trim()) return;

    const newTodo = {
      id: Date.now(),
      title: text,
      done: false,
    };

    setTodos([...todos, newTodo]);
    setText("");
  }

  function toggleTodo(id) {
    setTodos(
      todos.map(todo =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    );
  }

  function deleteTodo(id) {
    setTodos(todos.filter(todo => todo.id !== id));
  }

  return (
    <div>
      <h1>TodoList</h1>

      <input
        value={text}
        onChange={e => setText(e.target.value)}
        placeholder="请输入任务"
      />

      <button onClick={addTodo}>添加</button>

      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              onClick={() => toggleTodo(todo.id)}
              style={{
                textDecoration: todo.done ? "line-through" : "none",
                cursor: "pointer",
              }}
            >
              {todo.title}
            </span>

            <button onClick={() => deleteTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

核心讲解

jsx 复制代码
const [text, setText] = useState("");

保存输入框内容。

jsx 复制代码
const [todos, setTodos] = useState([]);

保存任务列表。

jsx 复制代码
setTodos([...todos, newTodo]);

不能直接 push,要创建新数组。

jsx 复制代码
todos.map(...)

用于更新某一项。

jsx 复制代码
todos.filter(...)

用于删除某一项。

面试追问

问:为什么不能直接 todos.push?

答:React 状态更新依赖引用变化。直接 push 会修改原数组,可能导致 React 无法正确感知变化。


五、实战项目 2:搜索过滤列表

jsx 复制代码
import { useMemo, useState } from "react";

function SearchList() {
  const [keyword, setKeyword] = useState("");

  const users = [
    { id: 1, name: "张三", role: "前端" },
    { id: 2, name: "李四", role: "后端" },
    { id: 3, name: "王五", role: "测试" },
  ];

  const filteredUsers = useMemo(() => {
    return users.filter(user =>
      user.name.includes(keyword) || user.role.includes(keyword)
    );
  }, [keyword]);

  return (
    <div>
      <h1>用户搜索</h1>

      <input
        value={keyword}
        onChange={e => setKeyword(e.target.value)}
        placeholder="请输入姓名或岗位"
      />

      <ul>
        {filteredUsers.map(user => (
          <li key={user.id}>
            {user.name} - {user.role}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default SearchList;

业务场景

  • 后台用户管理
  • 商品搜索
  • 订单筛选
  • 城市列表过滤

常见错误

jsx 复制代码
users.filter(...)

如果数据量很大,每次渲染都计算,可能影响性能。可以用 useMemo 缓存。


jsx 复制代码
import { useState } from "react";

function Modal({ open, title, children, onClose }) {
  if (!open) return null;

  return (
    <div style={styles.mask}>
      <div style={styles.modal}>
        <h2>{title}</h2>
        <div>{children}</div>
        <button onClick={onClose}>关闭</button>
      </div>
    </div>
  );
}

const styles = {
  mask: {
    position: "fixed",
    inset: 0,
    background: "rgba(0,0,0,0.4)",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
  },
  modal: {
    width: 400,
    padding: 24,
    background: "#fff",
    borderRadius: 8,
  },
};

function App() {
  const [open, setOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setOpen(true)}>打开弹窗</button>

      <Modal
        open={open}
        title="提示"
        onClose={() => setOpen(false)}
      >
        <p>这是一个可复用 Modal 组件。</p>
      </Modal>
    </div>
  );
}

export default App;

组件设计重点

jsx 复制代码
open

控制弹窗显示隐藏。

jsx 复制代码
title

弹窗标题。

jsx 复制代码
children

弹窗内容插槽。

jsx 复制代码
onClose

关闭回调。

面试追问

问:children 是什么?

答:children 是 React 中特殊的 prop,用于接收组件标签内部的内容。


七、组件封装教学

1. 如何拆分组件?

拆分原则:

txt 复制代码
一个组件只做一件事
重复出现的 UI 抽成组件
复杂页面按业务模块拆分

React 官方也建议构建 UI 时,先把界面拆成组件层级,再连接数据流。(React)


2. 通用 Button 组件

jsx 复制代码
function Button({ type = "primary", children, onClick, disabled = false }) {
  const style = {
    padding: "8px 16px",
    border: "none",
    borderRadius: 4,
    cursor: disabled ? "not-allowed" : "pointer",
    background: type === "primary" ? "#1677ff" : "#ddd",
    color: type === "primary" ? "#fff" : "#333",
  };

  return (
    <button style={style} onClick={onClick} disabled={disabled}>
      {children}
    </button>
  );
}

export default Button;

使用

jsx 复制代码
<Button type="primary" onClick={() => alert("提交")}>
  提交
</Button>

3. 通用 Card 组件

jsx 复制代码
function Card({ title, children }) {
  return (
    <div
      style={{
        border: "1px solid #eee",
        borderRadius: 8,
        padding: 16,
        marginBottom: 16,
      }}
    >
      <h3>{title}</h3>
      <div>{children}</div>
    </div>
  );
}

export default Card;

使用

jsx 复制代码
<Card title="用户信息">
  <p>姓名:张三</p>
  <p>岗位:前端工程师</p>
</Card>

八、进阶内容

1. 组件通信

常见方式:

txt 复制代码
父传子:props
子传父:回调函数
兄弟通信:状态提升
跨层级通信:Context
复杂状态:状态管理库

2. 状态提升

jsx 复制代码
import { useState } from "react";

function InputBox({ value, onChange }) {
  return (
    <input value={value} onChange={e => onChange(e.target.value)} />
  );
}

function Preview({ value }) {
  return <p>预览:{value}</p>;
}

function App() {
  const [text, setText] = useState("");

  return (
    <div>
      <InputBox value={text} onChange={setText} />
      <Preview value={text} />
    </div>
  );
}

export default App;

大白话

两个组件都需要同一份数据,就把数据放到它们共同的父组件。


3. Context

jsx 复制代码
import { createContext, useContext } from "react";

const ThemeContext = createContext("light");

function Toolbar() {
  const theme = useContext(ThemeContext);

  return <div>当前主题:{theme}</div>;
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

export default App;

场景

  • 主题
  • 登录用户信息
  • 多语言
  • 权限信息

4. React 性能优化

常见方案:

txt 复制代码
React.memo:缓存组件
useMemo:缓存计算结果
useCallback:缓存函数
合理使用 key
列表虚拟滚动
避免不必要的状态提升
组件拆分
按需加载

5. React Router

React Router 是 React 生态中常用的路由库。官方文档介绍 React Router v7 是非破坏性升级,并支持逐步衔接 React 19 相关能力。(reactrouter.com)

jsx 复制代码
import {
  createBrowserRouter,
  RouterProvider,
  Link,
} from "react-router-dom";

function Home() {
  return <h1>首页</h1>;
}

function About() {
  return <h1>关于我们</h1>;
}

function Layout() {
  return (
    <div>
      <Link to="/">首页</Link>
      <Link to="/about">关于</Link>
    </div>
  );
}

const router = createBrowserRouter([
  { path: "/", element: <Home /> },
  { path: "/about", element: <About /> },
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

6. 常见项目目录结构

txt 复制代码
src
├── assets        静态资源
├── components    通用组件
├── pages         页面组件
├── hooks         自定义 Hook
├── utils         工具函数
├── services      接口请求
├── router        路由配置
├── store         状态管理
├── App.jsx
└── main.jsx

九、React 高频面试题

1. React 为什么需要虚拟 DOM?

虚拟 DOM 是 JS 对象形式的 UI 描述。React 通过比较前后虚拟 DOM,尽量减少真实 DOM 操作。

大白话:

真实 DOM 很贵,虚拟 DOM 像"草稿纸",先在草稿纸上算清楚哪里变了,再去改真实页面。


2. key 的作用是什么?

key 帮助 React 识别列表中每一项的身份。

错误:

jsx 复制代码
list.map((item, index) => <li key={index}>{item.name}</li>)

更推荐:

jsx 复制代码
list.map(item => <li key={item.id}>{item.name}</li>)

3. useEffect 执行时机?

jsx 复制代码
useEffect(() => {
  console.log("每次渲染后执行");
});

useEffect(() => {
  console.log("只在挂载后执行一次");
}, []);

useEffect(() => {
  console.log("keyword 变化时执行");
}, [keyword]);

4. useMemo 和 useCallback 区别?

txt 复制代码
useMemo:缓存值
useCallback:缓存函数

等价理解:

jsx 复制代码
useCallback(fn, deps)

约等于:

jsx 复制代码
useMemo(() => fn, deps)

5. 受控组件和非受控组件?

受控组件:

jsx 复制代码
<input value={value} onChange={e => setValue(e.target.value)} />

非受控组件:

jsx 复制代码
<input ref={inputRef} />

6. React 组件通信方式?

txt 复制代码
父传子:props
子传父:回调函数
兄弟通信:状态提升
跨层级通信:Context
复杂全局状态:Redux / Zustand / Jotai 等

7. React 性能优化方案?

txt 复制代码
1. 使用 React.memo 减少组件重复渲染
2. 使用 useMemo 缓存复杂计算
3. 使用 useCallback 缓存函数
4. 避免无意义的 state
5. 大列表使用虚拟滚动
6. 路由懒加载
7. 合理拆分组件
8. 使用稳定 key

十、React 学习路线图

txt 复制代码
第一阶段:JavaScript 基础
变量、函数、数组、对象、ES6、模块化、异步

第二阶段:React 基础
JSX、组件、Props、State、事件、列表、表单

第三阶段:Hooks
useState、useEffect、useRef、useMemo、useCallback、自定义 Hook

第四阶段:项目实战
TodoList、搜索列表、Modal、后台管理系统

第五阶段:工程化
Vite、ESLint、Prettier、Git、接口请求、环境变量

第六阶段:进阶能力
Context、路由、权限、性能优化、组件封装

第七阶段:面试准备
虚拟 DOM、key、Hooks 原理、组件通信、性能优化

最后总结

React 学习不要只背概念。正确路线是:

txt 复制代码
先学组件
再学状态
再学 Hooks
再做项目
最后补面试原理

真正掌握 React 的标志不是"知道 useState 是什么",而是你能独立完成:

txt 复制代码
一个可维护的页面
一套可复用组件
一次清晰的数据流设计
一个能解释给面试官听的项目
相关推荐
是上好佳佳佳呀2 小时前
【前端(九)】CSS Transform 2D/3D 变换笔记:分清两个原点,搞懂多重变换
前端·css·笔记
|晴 天|10 小时前
Vue 3 + TypeScript + Element Plus 博客系统开发总结与思考
前端·vue.js·typescript
猫32811 小时前
v-cloak
前端·javascript·vue.js
旷世奇才李先生11 小时前
Vue 3\+Vite\+Pinia实战:企业级前端项目架构设计
前端·javascript·vue.js
SoaringHeart13 小时前
Flutter进阶:用OverlayEntry 实现所有弹窗效果
前端·flutter
IT_陈寒15 小时前
Vite静态资源加载把我坑惨了
前端·人工智能·后端
herinspace15 小时前
管家婆实用贴-如何分离和附加数据库
开发语言·前端·javascript·数据库·语音识别
小码哥_常15 小时前
从MVC到MVI:一文吃透架构模式进化史
前端
嗷o嗷o15 小时前
Android BLE 的 notify 和 indicate 到底有什么区别
前端