深入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补丁包进行渲染!!

相关推荐
真的很上进3 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
wakangda14 小时前
React Native 集成原生Android功能
javascript·react native·react.js
秃头女孩y20 小时前
【React中最优雅的异步请求】
javascript·vue.js·react.js
前端小小王1 天前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 天前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
不是鱼1 天前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
飞翔的渴望1 天前
antd3升级antd5总结
前端·react.js·ant design
爱喝奶茶的企鹅1 天前
Next.js 14 路由进阶:从约定式到动态路由的最佳实践
react.js
╰つ゛木槿2 天前
深入了解 React:从入门到高级应用
前端·react.js·前端框架
用户30587584891252 天前
Connected-react-router核心思路实现
react.js