1. React 原理
React 是一个用于构建用户界面的 JavaScript 库。它的核心原理包括以下几个关键概念:
- 声明式编程:React 允许您以声明式的方式描述界面,您只需声明界面应该如何根据不同的状态展示,而无需关心具体的实现细节。
- 组件化:React 的界面是由多个独立、可复用的组件构成的。每个组件管理自己的状态,并描述它们应该如何渲染。
- 虚拟 DOM(Virtual DOM) :React 为每个 DOM 对象维护了一个轻量级的虚拟 DOM 表示。任何状态变更都会首先反映在虚拟 DOM 上,然后 React 通过比较新旧虚拟 DOM 的差异,来决定如何高效地更新实际的 DOM。
- 响应式更新:当组件的状态改变时,React 会自动重新渲染组件及其子组件,以确保显示内容与状态同步。
2. 任务:实现基础数据渲染结构
一个index.html页面
App.jsx页面
main.js页面
3. 实现步骤
为了方便验证,不管代码怎么变化,"app" 这个字符串是会一直显示在网页上的
步骤 1: 创建基本 HTML 文档
首先,创建一个基本的 index.html 文件,作为应用的入口点。
index.html:
这个 HTML 文档包含一个空的 <div id="root"></div>,这是我们 React 应用的挂载点。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div> //入口
<script type="module" src="main.js"></script> // 引入main.js
</body>
</html>
步骤 2: 创建和渲染基本 DOM 元素
接着,创建 main.js 文件,并编写代码来手动创建和渲染 DOM 元素。
main.js:
// 这段代码创建了一个 `div` 元素并给它设置了 ID,然后创建了一个文本节点,并将这些元素添加到 `root` 元素内。
const dom = document.createElement('div');
dom.id = 'app';
const getRoot = document.getElementById('root');
getRoot.append(dom);
const el = document.createTextNode('');
el.nodeValue = 'app';
dom.append(el);
步骤 3: 用对象表示元素结构
将 DOM 元素的创建过程转换为使用对象表示。
main.js:
// 这个对象 (`reacteEl`) 描述了一个 `div` 元素,其中包含一个文本节点。这是 React 元素的简化表示。
const reacteEl = {
type: 'div',
props: {
id: "app",
children:[
{
type:"TEXT_ELEMENT",
props:{
nodeValue:'app',
children:[]
}
}
]
}
};
步骤 4: 提取子元素
为了更清晰地组织代码,将子元素提取为独立的变量。
main.js:
// `textEl` 是一个文本节点的描述,被作为子元素包含在 `reacteEl` 中。
const textEl = {
type:"TEXT_ELEMENT",
props:{
nodeValue:'app',
children:[]
}
};
const reacteEl = {
type: 'div',
props: {
id: "app",
children:[textEl]
}
};
步骤 5: 使用变量来创建和渲染 DOM
接下来,使用这些对象来创建和渲染实际的 DOM 元素。
main.js:
// 在这一步,`reacteEl` 和 `textEl` 对象被用来指导 DOM 元素的创建和渲染。
const dom = document.createElement(reacteEl.type);
dom.id = reacteEl.props.id;
const getRoot = document.getElementById('root');
getRoot.append(dom);
const el = document.createTextNode('');
el.nodeValue = textEl.props.nodeValue;
dom.append(el);
步骤 6: 封装创建文本节点的函数
为了提高代码的复用性和清晰性
- 封装一个
creatTextNode函数来创建文本节点对象。并且封装一个createElement用于创建任何类型的元素(包括文本元素)的对象表示。 - 创建App元素 :使用
createElement函数创建一个div元素,其 ID 为'app',并包含一个文本节点'app',根据App元素的类型创建一个真实的 DOM 元素,然后将其添加到页面的root元素内。
main.js:
function creatTextNode(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: []
}
};
}
function createElement(type, props, ...children) {
return {
type: type,
props: {
...props,
children
};
}
const App = createElement('div', { id: 'app' }, creatTextNode('app'));
const dom = document.createElement(App.type);
dom.id = App.props.id;
const getRoot = document.getElementById('root');
getRoot.append(dom);
const el = document.createTextNode('');
el.nodeValue = creatTextNode('app').props.nodeValue;
dom.append(el);
步骤 7: 继续改进
- 目前的代码结构中,文本节点被处理了两次:一次在
createElement函数中,一次在手动渲染时。为了优化,我们应该实现一个render函数来处理整个虚拟 DOM 树的渲染,而不是手动创建和添加每个节点。
main.js完整代码:
function creatTextNode(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: []
}
};
}
function createElement(type, props, ...children) {
return {
type: type,
props: {
...props,
children: children.map(child =>
typeof child === 'object' ? child : creatTextNode(child)) // 判断类型以便于可以直接传进来字符串
}
};
}
function render(virtualDom, container) {
const dom = virtualDom.type === 'TEXT_ELEMENT'
? document.createTextNode(virtualDom.props.nodeValue)
: document.createElement(virtualDom.type);
Object.keys(virtualDom.props).forEach(name => {
if (name !== 'children') {
dom[name] = virtualDom.props[name];
}
});
virtualDom.props.children.forEach(child => render(child, dom));
container.appendChild(dom);
}
const App = createElement('div', { id: 'app' }, 'app');
const rootDiv = document.getElementById('root');
render(App, rootDiv);
步骤 8: 模仿结构
- 目前的代码结构中,已经完成了基本的功能,但是为了保持和原react的结构一致,我们需要新增一个
createRoot方法,类似于 React 17+ 的新 API。 - 使用
ReactDOM.createRoot方法创建一个根节点,并渲染App到这个根节点上。
const ReactDOM = {
createRoot(container) {
return {
render(app) {
render(app, container);
}
};
}
};
const App = createElement('div', { id: 'app' }, 'app');
ReactDOM.createRoot(document.getElementById('root')).render(App);
main.js完整代码如下:
function creatTextNode(text){
return{
type:"TEXT_ELEMENT",
props:{
nodeValue:text,
children:[]
}
}
}
function createElement(type, props, ...children) {
return{
type: type,
props: {
...props,
children: children.map(child =>
typeof child === 'object' ? child : creatTextNode(child))
}
}
}
function render(virtualDom, container){
const dom = virtualDom.type === 'TEXT_ELEMENT'?document.createTextNode(virtualDom.props.nodeValue):document.createElement(virtualDom.type)
Object.keys(container).forEach((child)=>{
virtualDom[child] = virtualDom[child]
})
virtualDom.props.children.forEach((child)=>render(child, dom))
container.appendChild(dom);
}
const ReactDOM ={
createRoot(constainer){
return {
render(App){
render(App, constainer)
}
}
}
}
const App = createElement('div', { id: 'app' }, 'app');
ReactDOM.createRoot(document.getElementById('root')).render(App)
到现在代码的部分已经好了 我们需要模仿数据结构把代码都提取出去
- 新建一个 cor 文件夹,下面新建一个react.js和reactDOM.js文件
- 新建App.js,和react之前的结构保持一致
结构如下:
React.js 代码:
function createNodeValue(text){
return {
type:'TEXT_ELEMENT',
props:{
nodeValue:text,
children:[]
}
}
}
function createEl(type, props, ...children){
return {
type: type,
props:{
...props,
children: children.map(child =>
typeof child === 'object' ? child : creatTextNode(child))
)
}
}
}
function render(virtualDom, container){
const dom = virtualDom.type === 'TEXT_ELEMENT'?document.createTextNode(virtualDom.props.nodeValue):document.createElement(virtualDom.type)
Object.keys(virtualDom.props).forEach((key)=>{
if(key !== "children"){
dom[key] = virtualDom.props[key]
}
})
if(virtualDom.props.children.length !== 0){
virtualDom.props.children.forEach((child)=>{
render(child, dom)
})
}
container.append(dom)
}
const React ={
render,
createEl
}
export default React
ReactDOM.js 代码:
import React from "./React.js"
const ReactDom = {
createRoot(container){
return {
render(App){
React.render(App, container)
}
}
}
}
export default ReactDom
App.js 代码
import React from "./core/React.js"
const App = React.createEl("div", { id: 'app'}, 'app','777')
export default App
main.js
import ReactDom from "./core/ReactDOM.js"
import App from './App.js'
ReactDom.createRoot(document.getElementById("root")).render(App)
index.html 代码不变
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="main.js"></script>
</body>
</html>
把 js 变成支持jsx语法
- 安装 vite : npm create vite 重新命名:
2.安装依赖: npm install
3.清空vite文件的内容,把之前的内容放进去,App.js和mian.js文件改成 jsx格式
4. 一些小的改动: App.jsx:
import React from './cor/React.js'
// const App = React.createElement('div', { id: 'app' }, 'app');
const App = <div>hi-mini-react</div>
export default App
有个遗留问题: 我们现在还没有实现支持App的function