在 React 18 中,createRoot 是用于创建 React 根节点的函数,它允许你将 React 组件渲染到浏览器 DOM 节点中。
const root = createRoot(document.getElementById('root'))
root.render(<Gallery />);
第一步:const root = createRoot(document.getElementById('root'))
作用:
这行代码的作用是创建一个 React 根节点(root),并将该根节点与页面中的某个 DOM 元素关联起来。
document.getElementById('root') 获取了 HTML 中 id 为 root 的元素,这个元素通常是
<div id="root"></div>。
createRoot(domNode) 接收一个 DOM 节点作为参数,并返回一个包含 render 和 unmount 方法的对象。
第二步:root.render(<Gallery />)
作用:
这行代码的作用是将 React 组件 <Gallery /> 渲染到之前创建的根节点中。
render 方法会把 JSX(即 <Gallery />)转换成虚拟 DOM,并最终更新实际的 DOM 结构。
测试脚本如下
src/Gallery.js:
export default function Gallery() {
return (
<section>
<h1>鼓舞人心的雕塑</h1>
<Image />
<Image />
<Image />
<Image />
</section>
);
}
function Image() {
return (
<img
src="https://i.imgur.com/ZF6s192.jpg"
alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
/>
);
}
src/index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import Gallery from './Gallery';
const root = ReactDOM.createRoot(document.getElementById('root88'));
root.render(
<React.StrictMode>
<Gallery />
</React.StrictMode>
);
public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen">
<div id="root88" class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto">
</body>
</html>

在一个 React 应用中一次屏幕更新都会发生以下三个步骤:
触发:触发一次渲染
渲染:React 渲染你的组件
提交:React 把更改提交到 DOM 上
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React 渲染与提交学习实例</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen">
<div id="root" class="container mx-auto px-4 py-8"></div>
<script type="text/babel">
const { useState, useEffect } = React;
// 学习记录项组件
const LearningItem = ({ item, onDelete, onEdit }) => {
const [isEditing, setIsEditing] = useState(false);
const [editValue, setEditValue] = useState(item.text);
const handleSave = () => {
if (editValue.trim()) {
onEdit(item.id, editValue);
setIsEditing(false);
}
};
return (
<div className="bg-white rounded-xl shadow-md p-4 mb-3 transition-all duration-300 hover:shadow-lg">
<div className="flex items-start">
<div className="flex-1">
{isEditing ? (
<div className="flex flex-col space-y-2">
<input
type="text"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
className="border-b-2 border-indigo-300 focus:border-indigo-500 outline-none pb-1 w-full"
autoFocus
/>
<div className="flex space-x-2 pt-2">
<button
onClick={handleSave}
className="px-3 py-1 bg-green-500 text-white rounded-lg text-sm hover:bg-green-600 transition-colors"
>
保存
</button>
<button
onClick={() => setIsEditing(false)}
className="px-3 py-1 bg-gray-300 text-gray-700 rounded-lg text-sm hover:bg-gray-400 transition-colors"
>
取消
</button>
</div>
</div>
) : (
<div>
<p className="text-gray-800">{item.text}</p>
<p className="text-xs text-gray-500 mt-1">
<i className="far fa-clock mr-1"></i>
{new Date(item.timestamp).toLocaleString('zh-CN')}
</p>
</div>
)}
</div>
{!isEditing && (
<div className="flex space-x-2 ml-2">
<button
onClick={() => setIsEditing(true)}
className="text-blue-500 hover:text-blue-700 transition-colors"
title="编辑"
>
<i className="fas fa-edit"></i>
</button>
<button
onClick={() => onDelete(item.id)}
className="text-red-500 hover:text-red-700 transition-colors"
title="删除"
>
<i className="fas fa-trash-alt"></i>
</button>
</div>
)}
</div>
</div>
);
};
// 主应用组件
const App = () => {
const [inputValue, setInputValue] = useState('');
const [items, setItems] = useState([]);
const [filter, setFilter] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
// 从本地存储加载数据
useEffect(() => {
const savedItems = localStorage.getItem('learningItems');
if (savedItems) {
setItems(JSON.parse(savedItems));
}
}, []);
// 保存数据到本地存储
useEffect(() => {
localStorage.setItem('learningItems', JSON.stringify(items));
}, [items]);
// 提交新学习记录
const handleSubmit = (e) => {
e.preventDefault();
if (inputValue.trim()) {
const newItem = {
id: Date.now(),
text: inputValue.trim(),
timestamp: new Date().toISOString()
};
setItems([newItem, ...items]);
setInputValue('');
}
};
// 删除学习记录
const handleDelete = (id) => {
setItems(items.filter(item => item.id !== id));
};
// 编辑学习记录
const handleEdit = (id, newText) => {
setItems(items.map(item =>
item.id === id ? {...item, text: newText} : item
));
};
// 清空所有记录
const handleClearAll = () => {
if (window.confirm('确定要清空所有学习记录吗?')) {
setItems([]);
}
};
// 过滤和搜索逻辑
const filteredItems = items.filter(item => {
const matchesSearch = item.text.toLowerCase().includes(searchTerm.toLowerCase());
return matchesSearch;
});
const completedCount = items.length;
const todayCount = items.filter(item =>
new Date(item.timestamp).toDateString() === new Date().toDateString()
).length;
return (
<div className="max-w-2xl mx-auto">
{/* 头部 */}
<header className="text-center mb-10">
<h1 className="text-4xl font-bold text-indigo-700 mb-2">
<i className="fas fa-book-reader mr-3"></i>学习记录系统
</h1>
<p className="text-gray-600">记录你的每一个学习瞬间</p>
</header>
{/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div className="bg-gradient-to-r from-indigo-500 to-purple-600 rounded-xl p-5 text-white shadow-lg">
<div className="flex items-center">
<i className="fas fa-tasks text-3xl mr-4"></i>
<div>
<p className="text-sm opacity-80">总计记录</p>
<p className="text-3xl font-bold">{completedCount}</p>
</div>
</div>
</div>
<div className="bg-gradient-to-r from-green-500 to-blue-600 rounded-xl p-5 text-white shadow-lg">
<div className="flex items-center">
<i className="fas fa-calendar-day text-3xl mr-4"></i>
<div>
<p className="text-sm opacity-80">今日新增</p>
<p className="text-3xl font-bold">{todayCount}</p>
</div>
</div>
</div>
</div>
{/* 输入表单 */}
<div className="bg-white rounded-2xl shadow-xl p-6 mb-8">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="learningInput" className="block text-lg font-medium text-gray-700 mb-2">
添加新的学习记录
</label>
<div className="flex space-x-2">
<input
id="learningInput"
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="今天学到了什么?"
className="flex-1 px-4 py-3 border-2 border-gray-200 rounded-xl focus:border-indigo-500 focus:outline-none transition-colors"
/>
<button
type="submit"
disabled={!inputValue.trim()}
className={`px-6 py-3 rounded-xl font-semibold transition-all ${
inputValue.trim()
? 'bg-indigo-600 text-white hover:bg-indigo-700 transform hover:-translate-y-0.5 shadow-md hover:shadow-lg'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}`}
>
<i className="fas fa-plus-circle mr-2"></i>提交
</button>
</div>
</div>
</form>
</div>
{/* 控制面板 */}
<div className="bg-white rounded-2xl shadow-xl p-6 mb-8">
<div className="flex flex-col md:flex-row md:items-center md:justify-between space-y-4 md:space-y-0">
<div className="flex items-center space-x-4">
<span className="text-gray-700 font-medium">搜索:</span>
<div className="relative">
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="查找学习记录..."
className="pl-10 pr-4 py-2 border-2 border-gray-200 rounded-lg focus:border-indigo-500 focus:outline-none w-full md:w-64"
/>
<i className="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
</div>
<div className="flex items-center space-x-4">
<span className="text-gray-700 font-medium">操作:</span>
<button
onClick={handleClearAll}
disabled={items.length === 0}
className={`px-4 py-2 rounded-lg flex items-center ${
items.length > 0
? 'bg-red-500 text-white hover:bg-red-600'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}`}
>
<i className="fas fa-trash-alt mr-2"></i>清空全部
</button>
</div>
</div>
</div>
{/* 学习记录列表 */}
<div className="bg-white rounded-2xl shadow-xl p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-gray-800">
<i className="fas fa-list mr-2"></i>学习记录列表
</h2>
<span className="bg-indigo-100 text-indigo-800 px-3 py-1 rounded-full text-sm">
共 {filteredItems.length} 条记录
</span>
</div>
{filteredItems.length === 0 ? (
<div className="text-center py-12">
<i className="fas fa-inbox text-5xl text-gray-300 mb-4"></i>
<h3 className="text-xl font-medium text-gray-500 mb-2">暂无学习记录</h3>
<p className="text-gray-400">
{items.length === 0
? '开始添加你的第一条学习记录吧!'
: '没有找到匹配的记录'}
</p>
</div>
) : (
<div className="space-y-3">
{filteredItems.map(item => (
<LearningItem
key={item.id}
item={item}
onDelete={handleDelete}
onEdit={handleEdit}
/>
))}
</div>
)}
</div>
{/* 页脚 */}
<footer className="mt-12 text-center text-gray-500 text-sm">
<p>© 2026 学习记录系统 - 记录成长的每一步</p>
</footer>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>

浙公网安备 33010602011771号