深入React源码03——Jsx的知识

1. JSX基础知识

大小驼峰:PasalCase、CamelCase、kabab-case、xxx_xxx

JSX(javascript and xml\html): 把JS与HTML标签混合在了一起(不是字符串拼接,❌element = "<div>123</div>"

🎈Tips: vscode如何支持JSX语法(格式化、快捷提示...)

  • 后缀名设置为jsx
  • webpack打包的规则中,也是会对.jsx这种文件,按照JS的方式进行处理的

1.1.语法特性

JSX语法具备过滤效果(过滤非法内容),有效防止XSS攻击(扩展思考:总结常见的XSS攻击和预防方案?)

1.1.1.一个根容器

最外层只能一个根容器(一个app有一个容器)

在ReactDOM.createRoot()的时候,不能直接把HTML/BODY做为根容器,需要指定一个额外的盒子「例如:#root

1.1.2.一个根元素节点

容器可以有多个视图、每一个视图,只有一个"根节点"。

  • 出现多个根节点则报错 Adjacent JSX elements must be wrapped in an enclosing tag.
  • React.Fragment 空文档标记标签:<></>(既保证只有一个根节点,又不新增一个HTML层级结构!!)

1.1.3 动态绑定数据{}

JS表达式嵌入到HTML中,需要使用{} (❗大括号中存放的是JS表达式)

JS表达式:执行有结果的表达式,如:数值、数学运算、三元运算符、循环(借助数组迭代的办法map,foreach没有返回值所以不行)

if(){}不行,JSX中进行的判断一般都要基于三元运算符来完成

命令式循环forfor/infor/ofwhile不行

1.1.4 JS表达式要求

{}语法中嵌入不同的值,所呈现出来的特点

  1. number/string:值是啥,就渲染出来啥

  2. boolean/null/undefined/Symbol/BigInt:渲染的内容是空

  3. 除数组对象外,其余对象一般都不支持在{}中进行渲染,但是也有特殊情况:

    • JSX虚拟DOM对象
    • 给元素设置style行内样式,要求必须写成一个对象格式
  4. 数组对象:把数组的每一项都分别拿出来渲染「并不是变为字符串渲染,中间没有逗号」

    JSX中遍历数组中的每一项,动态绑定多个JSX元素,一般都是基于数组中的map来实现的

    和vue一样,循环绑定的元素要设置key值(作用:用于DOM-DIFF差异化对比)

  5. 函数对象:不支持在{}中渲染,但是可以作为函数组件,用方式渲染。

1.1.5 给元素设置样式

  1. 行内样式:需要基于对象的格式处理,直接写样式字符串会报错;
js 复制代码
<h2 style={{
  color: 'red',
  fontSize: '18px' //样式属性要基于驼峰命名法处理
}}>
  1. 设置样式类名:需要把class替换为className;
html 复制代码
<h2 className="box">

1.2 核心语法初识

为了可以看懂代码于是这里先讲讲一些语法核心。

jsx 复制代码
import React from 'react'; //React语法核心
import ReactDOM from 'react-dom/client'; //构建HTML(WebApp)的核心

//获取页面中#root的容器,作为"根"容器
const root = ReactDOM.createRoot(document.getElementById('root'));

//基于render方法渲染我们编写的视图,把渲染后的内容,全部插入到#root中进行渲染
root.render(
        ....
);

这样就能看懂练手了。😊

1.2 练手

jsx 复制代码
//src\index.jsx(index.js改过来的)
// 项目经过瘦身,保留文中内容就行
import React from 'react';
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));

/* 需求一:基于数据的值,来判断元素的显示隐藏 */
let flag = false,isRun = false;

//渲染根函数,注意区分两种写法的区别。
root.render(
    <>
        {/!* 控制元素的display样式:不论显示还是隐藏,元素本身都渲染出来了 *!/}
        <button style={{
            display: flag ? 'block' : 'none'
        }}>按钮1</button>

        <br />

        {/!* 控制元素渲染或者不渲染 *!/}
        {flag ? <button>按钮2</button> : null}

        <br />

        <button>{isRun ? '正在处理中...' : '立即提交注册'}</button>
    </>
);


/* 需求二:从服务器获取了一组列表数据,循环动态绑定相关的内容 */
let data = [{
    id: 1,
    title: 'zono1'
}, {
    id: 2,
    title: 'zono2'
}, {
    id: 3,
    title: 'zono3'
}];//虚拟数据,假装从后端拿来

root.render(
    <>
        <h2 className="title">今日新闻</h2>
        <ul className="news-box">
            {data.map((item, index) => {
                /* 循环创建的元素一定设置key属性,属性值是本次循环中的"唯一值"「优化DOM-DIFF」 */
                return <li key={item.id}>
                    <em>{index + 1}</em>
                    &nbsp;&nbsp;
                    <span>{item.title}</span>
                </li>;
            })}
        </ul>

        <br />

        {/* 扩展需求:没有数组,就是想单独循环五次 */}
        {new Array(5).fill(null).map((_, index) => {
            return <button key={index}>
                按钮{index + 1}
            </button>;
        })}
    </>
);

2. JSX底层渲染机制

使用babel转换工具,可以直观看见。

第一步:JSX→virtualDOM

运行时会把我们编写的JSX语法编译为虚拟DOM对象(virtualDOM)。

虚拟DOM对象:框架自己内部构建的一套对象体系(对象的相关成员都是React内部规定的),基于这些属性描述出我们所构建视图中的DOM节点的相关特征。

具体流程

babeljs.io/repl可以在线观看j...

下面是我编写的例子

js 复制代码
//jsx
<>
  <h2 className="title" style={styObj}>123</h2>
  <div className ="box">
    123
    <span>321</span>
    <span>zono</span>
  </div>
</>


//js
//Classic模式,babel官网的另一个模式不是很懂
//TODO 了解模式
React.createElement(
  React.Fragment,
  null,
  React.createElement(
      "h2",
    {className: "title",
     style: styObj,},
    "123"
  ),
  React.createElement(
    "div",
    {className: "box",},
    "123",
    React.createElement("span", null, "321"),
    React.createElement("span", null, "zono")
  )
);

//Automatic模式(目前不做解释)
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
import { Fragment as _Fragment } from "react/jsx-runtime";
_jsxs(_Fragment, {
  children: [
    _jsx("h2", {
      className: "title",
      style: styObj,
      children: "123",
    }) ,
    _jsxs("div", {
      className: "box",
      children: [
        "123" ,
        _jsx("span", {
          children: "321",
        }),
         _jsx("span", {
          children: "zono",
        }),
      ],
    }),
  ],
});

让我们来解读一下:

  1. 基于babel-preset-react-app(一个专门对react的babel) 把JSX编译为React.createElement(...) 格式。元素节点都会基于createElement进行处理!

出现的函数的解释:

createElement -- React 中文文档 (docschina.org)

React.createElement(ele,props,...children)

  • ele:元素标签名\组件名

  • props:元素的属性集合(对象)(无属性,则此值为null)

  • children:当前元素的子节点,第三个及以后的参数都是

  • 返回虚拟DOM,可以看下文虚拟DOM结构

  1. 感觉Automatic模式就是表示的更清楚。
  2. 注意该方法开发时并不常用,有jsx就够了
  1. 再把 createElement 方法执行,创建出virtualDOM虚拟DOM对象(JSX元素、JSX对象、ReactChild对象...)!!

    我们可以把React.createElement打印出来看看效果。

json 复制代码
// 大概结构 
virtualDOM = {
    $$typeof: Symbol(react.element),//组件类型,官网组件https://react.docschina.org/reference/react-dom/components
    ref: null,
    key: null,
    type: 标签名「或组件」, //h1、h2...
    // 存储了元素的相关属性 && 子节点信息
    props: {
        元素的相关属性,如:className、style,
        children:子节点信息(没有子节点则没有这个属性、属性值可能是一个值、也可能是一个数组)
    }//必定存在,至少是个空对象
  }

实操(非必要)

这里将简单编写一个creatElement函数

感觉太硬了,所以分个p,见04.jsx底层实操

jsx 复制代码
//下面是官方笔记,不确定能不能跑通
const createElement = function createElement(type, props, ...children) {
    // virtual:[ˈvɜːtʃuəl] 
    let len = children.length;
    let virtualDOM = {
        type,
        props: {}
    };
    if (props !== null) virtualDOM.props = { ...props };
    if (len === 1) virtualDOM.props.children = children[0];
    if (len > 1) virtualDOM.props.children = children;
    return virtualDOM;
};

第二步:virtualDOM→真实DOM

真实DOM:浏览器页面中,最后渲染出来,让用户看见的DOM元素(js基础内容)

​ 这一步是基于ReactDOM中的render方法处理的

v16(16版本)写法

jsx 复制代码
ReactDOM.render(
    <>...</>,
    document.getElementById('root')
);

v18

jsx 复制代码
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <>...</>
);

实操(非必要)

render

推荐看04

javascript 复制代码
const render = function render(virtualDOM, container) {
    let { type, props } = virtualDOM;
    if (typeof type === "string") {
        let element = document.createElement(type);
        for (let key in props) {
            if (!props.hasOwnProperty(key)) break;
            if (key === 'className') {
                element.setAttribute('class', props[key]);
                continue;
            }
            if (key === 'style') {
                let styOBJ = props['style'];
                for (let attr in styOBJ) {
                    if (!styOBJ.hasOwnProperty(attr)) break;
                    element.style[attr] = styOBJ[attr];
                }
                continue;
            }
            if (key === 'children') {
                let children = props['children'];
                if (!Array.isArray(children)) children = [children];
                children.forEach(item => {
                    if (typeof item === "string") {
                        element.appendChild(document.createTextNode(item));
                        return;
                    }
                    render(item, element);
                });
                continue;
            }
            element.setAttribute(key, props[key]);
        }
        container.appendChild(element);
    }
};

更新:新老virtual对比,然后更新对应内容

第一次渲染页面是直接从virtualDOM->真实DOM;

后期视图更新时,会经过一个DOM-DIFF的对比,计算出补丁包PATCH(两次视图差异的部分),把PATCH补丁包进行渲染!!

相关推荐
TonyH200211 小时前
webpack 4 的 30 个步骤构建 react 开发环境
前端·css·react.js·webpack·postcss·打包
掘金泥石流12 小时前
React v19 的 React Complier 是如何优化 React 组件的,看 AI 是如何回答的
javascript·人工智能·react.js
lucifer31114 小时前
深入解析 React 组件封装 —— 从业务需求到性能优化
前端·react.js
秃头女孩y19 小时前
React基础-快速梳理
前端·react.js·前端框架
sophie旭21 小时前
我要拿捏 react 系列二: React 架构设计
javascript·react.js·前端框架
BHDDGT1 天前
react-问卷星项目(5)
前端·javascript·react.js
liangshanbo12151 天前
将 Intersection Observer 与自定义 React Hook 结合使用
前端·react.js·前端框架
黄毛火烧雪下1 天前
React返回上一个页面,会重新挂载吗
前端·javascript·react.js
BHDDGT2 天前
react-问卷星项目(4)
前端·javascript·react.js
xiaokanfuchen862 天前
React中Hooks使用
前端·javascript·react.js