[ React 入门 | 青训营笔记 ]

80 阅读6分钟

React 入门

观看超哥视频做的笔记:www.bilibili.com/video/BV1bS…

1. React 简介

为什么学?

  1. 原生 JS 操作 DOM 繁琐,效率低
  1. 使用 JS 直接操作 DOM,浏览器会进行大量的重绘重排
  1. 原生 JS 没有组件化编码方案,代码复用低

React 特点:

  1. 虚拟 DOM
  2. 声明式
  3. 基于组件
  4. 支持服务器端渲染

React 的一些主要优点?

  1. 它提高了应用的性能
  2. 可以方便在客户端和服务器端使用
  3. 由于使用 JSX,代码的可读性更好
  4. 使用 React,编写 UI 测试用例变得非常容易

2. React 基础案例

  1. 首先需要引入几个 react 包
  • React 核心库、操作 DOM 的 react 扩展库、将 jsx 转为 js 的 babel 库 【先引入react.development.js,后引入react-dom.development.js】
react.development.js
react-dom.development.js
  • react-dom 是 react 的 dom 包,使用 react 开发 web 应用时必须引入
  • 下载地址:unpkg.com/react-dom@1…
babel.min.js
  • 浏览器不能正常识别 JSX,所以当我们使用 JSX 时,还必须引入 babel来完成对代码的编译,将 JSX 转化为 JS。
  • babel 下载地址:unpkg.com/babel-stand…
  1. 创建一个容器
  1. 创建虚拟DOM,渲染到容器中
<!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>Hello World</title>
    <!-- 引入 react 核心库 -->
    <script type="text/javascript" src="script/react.development.js"></script>
    <!-- 引入 react 的 DOM 库 -->
    <script type="text/javascript" src="script/react-dom.development.js"></script>
</head><body>
    <div id="root"></div>
    <script>
        // 1. 创建一个react元素
        const h1 = React.createElement('h1', {}, 'Hello')
        // 2. 获取根元素对应的react元素
        const root = ReactDOM.createRoot(document.getElementById('root'))
        // 3. 将div渲染到根元素中
        root.render(h1)
    </script>
</body>
</html>

新方式:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>hello_react</title>
  </head>
  <body>
    <!-- 准备好一个“容器” -->
    <div id="test"></div>
​
    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
​
    <script type="text/babel">
      /* 此处一定要写babel */
      //1.创建虚拟DOM
      const VDOM = <h1>Hello</h1> 
      //2.渲染虚拟DOM到页面
      const root = ReactDOM.createRoot(document.querySelector('#test'))
      root.render(VDOM)
    </script>
  </body>
</html>

💡 三个API:

  • React.createElement()

    • React.createElement(type, [props], [...children])

    • 用来创建 React 元素,React 元素无法修改

      1. React元素最终会通过虚拟DOM转换为真实的DOM元素
      2. React元素一旦创建就无法修改,只能通过新创建的元素进行替换
    • 参数:

      1. 元素名(组件名,html 标签必须小写)
      1. 元素中的属性

        • 设置事件时,属性名需要修改为驼峰命名法
        • class 属性需要使用 className 来设置
      1. 元素的子元素(内容)
  • ReactDOM.createRoot()

    • createRoot(container[, options])
    • 用来创建 React 的根容器,容器用来放置 React 元素
    • 需要一个 DOM 元素作为参数
  • ReactDOM.render()

    • root.render(element)

    • 用来将 React 元素渲染到根元素中,当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 React 的 DOM 差分算法(DOM diffing algorithm)进行高效的更新。

      • 根元素中所有的内容都会被删除,被 React 元素所替换
      • 当重复调用 render() 时,React 会将两次的渲染结果进行比较,
      • 它会确保只修改那些发生变化的元素,对 DOM 做最少的修改
    • 不会修改容器节点(只会修改容器的子节点)。可以在不覆盖现有子节点的情况下,将组件插入已有的 DOM 节点中。

<button id="btn">我是按钮</button>
<div id="root"></div>
<script>
    
    const button = React.createElement('button', {
        type: 'button',
        // class 在 react 中是类组件关键字,class 属性需要使用 className 来设置
        className: 'hello',
        // 设置事件时,属性名需要修改为驼峰命名法,而且事件回调要用函数表示
        onClick: () => { alert("你好啊~") }
    }, '点我一下')
    
    // 元素内容可以放子元素、文本等,逗号隔开
    const div = React.createElement('div', {}, '我是一个div', button)
    
    // ReactDOM.render(div, document.getElementById('root')); // 老版本的React中使用方法
    const root = ReactDOM.createRoot(document.getElementById('root'))
    root.render(div)
    
    const btn = document.getElementById('btn')
    btn.addEventListener('click', () => {
        // React 元素一经创建,无法修改,只能新建元素进行替换
        const button = React.createElement('button', {
            type: 'button',
            className: 'hello',
            onClick: () => { alert("你好啊~") }
        }, 'click me')
​
        const div = React.createElement('div', {}, '我是一个div', button)
​
        // 修改React元素后,必须重新对根元素进行渲染
        // 当调用render渲染页面时,React会自动比较两次渲染的元素,只在真实DOM中更新发生变化的部分,没发生变化的保持不变
        root.render(div)
    })
</script>

3. JSX 语法

JSX 是 JavaScript 的语法扩展,JSX 使得我们可以以类似于 HTML 的形式去使用 JS。JSX 是 React 中声明式编程(简单理解就是以结果为导向的编程)的体现方式。使用 JSX 将我们所期望的网页结构编写出来,然后 React 再根据 JSX 自动生成 JS 代码。所以我们所编写的 JSX代码,最终都会转换为以调用React.createElement()创建元素的代码。

浏览器并不能正常识别 JSX, JSX 最终需要转换为 JS 代码执行。JSX 是 React.createElement() 的语法糖,React 内部其实是通过 babel 将 JSX 转换为 JS 代码。

<!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>JSX</title>
    <!-- 引入 react 核心库 -->
    <script type="text/javascript" src="script/react.development.js"></script>
    <!-- 引入 react 的 DOM 库 -->
    <script type="text/javascript" src="script/react-dom.development.js"></script>
    <!-- 引入 babel,将 jsx 转为 js -->
    <script type="text/javascript" src="script/babel.min.js"></script>
</head><body>
    <div id="root"></div>
    <!--设置js代码被babel处理-->
    <script type="text/babel">
        // 创建一个 React 元素中的属性
        // 命令式编程
        // const button = React.createElemet('button', {}, '我是按钮')
​
        // 声明式编程
        // 在 React 中可以通过 JSX(JS扩展)来创建 React 元素,JSX 需要被翻译成 JS 代码,才可以被 React 执行
        // 要在 React 中使用 JSX,必须引入 babel 来完成“翻译”工作
        const button = <button>我是按钮</button>
        const root = ReactDOM.createRoot(document.getElementById('root'))
        root.render(button)
    </script></body></html>

👇 注意点:

  1. JSX 不是字符串,不要加引号

  2. JSX 的 html 标签应该小写,React 组件应该大写开头

  3. 不能有多个根标签,只能有一个根标签

  4. 标签中使用 {} 嵌入 JS 表达式

    id = {myId.toUpperCase()}
    

    关于JS表达式和JS语句:

    • JS表达式:返回一个值,可以放在任何一个需要值的地方 a a+b demo(a) arr.map() function text(){}
    • JS语句:if(){} for(){} while(){} swith(){} 不会返回一个值
  5. JSX 的标签必须正确结束(自结束标签必须写/)

    <input type="text"/>
    
  6. 布尔类型、Null 以及 Undefined 将会忽略

  7. 在 JSX 中,属性可以直接在标签中设置

    const div = <div
        id="box"
        onClick={() => {
            alert('哈哈')
        }}
        className="box1"
        style={{backgroundColor: "yellowgreen", border: '10px red solid'}}
        >
    </div>;
    
    • class 需要使用 className 代替
    • style 必须使用对象设置({{}}、驼峰命名)
  1. 注释要写在花括号里

    const div = <div>
    {/*这里注释*/}
    </div>;
    
  2. 数组

    JSX 允许在模板中插入数组,数组自动展开全部成员

    const arr = [
      <h1>小丞</h1>,
      <h2>同学</h2>,
    ];
    const div = <div>
        {arr}
    </div>
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(div);      
    

    {} 只能用来放 js 表达式,而不能放语句(if for), 但在语句中可以去操作 JSX

    <div id="root"></div>
    <script type="text/babel">
        const data = ['a', 'b', 'c']
        const div = <div>
            <ul>
                {data.map((item, index) => <li key={index}>{item}</li>)}
            </ul>
        </div >
        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(div);
    </script>
    

    65435e02b82583d88d964485d6c1f79.png

4. 虚拟 DOM

在 React 中,我们操作的元素被称为 React 元素。React 通过虚拟 DOM 将 React 元素 和 原生 DOM 进行映射,虽然操作的 React 元素,但是这些操作最终都会在真实 DOM 中体现出来

虚拟DOM的好处:

  1. 降低 API 复杂度
  2. 解决兼容问题
  3. 提升性能(减少 DOM 的不必要操作)

每当我们调用 root.render() 时,页面就会发生重新渲染

React 会通过 diff 算法,将新的元素和旧的元素进行比较,只对变化的元素进行修改,没有发生的变化不予处理

diff 算法: 原文链接:blog.csdn.net/weixin_4270…

  1. 比较两个虚拟 dom 树,对根节点 root 进行执行 patch(oldVnode,newVnode) 函数,比较两个根节点是否是相同节点。如果不同,直接替换(新增新的,删除旧的)

    • 比较节点的 sel 选择器(标签名、id、class)、key 是否一致(都没有定义 key 的话,都为 undefined 也是相等的)
  2. 如果相同,对两个节点执行 patchVnode(oldVnode, newVnode) ,比较属性,文本,以及子节点。(新增,删除,修改文本内容)。

    • 当都存在子节点时,并且 oldVnode === newVnode 为 false 时(如果为 true,代表子级肯定也完全相等),执行 updateChildren 函数,去进一步比较他们的子节点。
  3. 首先会将新旧 vnode 的子节点提取出来,并分别加上两个指针oldStart, oldEnd, newStart, newEnd。分别指向第一个节点和最后一个节点。updateChildren 函数的比较分 3 大类。

    • 如果 oldStart === newStartoldStart === newEndoldEnd === newStartoldEnd === newEnd 这4种情况中任何一种匹配(这里的等于应该是 sel 选择器和 key)。则会执行 patchVnode 进一步比较。同时指针往中间移动。

    • oldStart > oldEnd 或者 newStart > newEnd 时。表示匹配结束。此时,多余的元素删除,新增的元素新增。

    • 上面几种情况都不匹配。这个时候 key 是否存在,起到关键性作用了。

      1. 存在 key 时,直接通过 key 去找到节点原来的位置。如果没有找到,就新增节点。找到了,就移动节点位置,进行比较。查找效率非常高。

      2. 没有 key,直接新增这个节点。这就导致这个节点下的所有子节点都会被重新新增。会出现明显的性能损耗。

      3. 比如新旧 dom 中列表的顺序发生改变时,有唯一标识的 key,可以得到性能的优化。

        下面的例子中,key 在列表中是唯一的,所以在进行 diff 算法比较时,只需要更新唐僧所在的子节点。若没有唯一 key,则四个 li 标签都需要进行更新。

        <body>
            <button id="btn">点我一下</button>
            <hr>
            <div id="root"></div>
        ​
            <script type="text/babel">
                //创建一个数据
                const data = ['孙悟空', '猪八戒', '沙和尚'];
                // 创建列表
                const list = <ul>
                    {/*data.map(item => <li key={item}>{item}</li>)*/}
                    {data.map((item, index) => <li key={index}>{item}</li>)}
                </ul>;
                // 获取根元素
                const root = ReactDOM.createRoot(document.getElementById('root'));
                // 渲染元素
                root.render(list);
                
                document.getElementById('btn').onclick = function () {
                    // 重新渲染页面
                    //创建一个数据
                    const data = ['唐僧', '孙悟空', '猪八戒', '沙和尚'];
                    // 创建列表
                    const list = <ul>
                        {data.map((item, index) => <li key={item}>{item}</li>)}
                    </ul>;
                    // 渲染元素
                    root.render(list);
        ​
            </script>
        </body>