20230522----重返学习-原生事件进阶-React中的合成事件-React中的样式私有化

day-075-seventy-five-20230522-原生事件进阶-React中的合成事件-React中的样式私有化

原生事件进阶

DOCTYPE html>
<html>
  <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>事件委托title>
    <link rel="stylesheet" href="src/assets/reset.min.css" />
    <style>
      html,
      body {
        height: 100%;
        overflow: hidden;
      }

      .center {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }

      #root {
        width: 300px;
        height: 300px;
        background: lightblue;
      }

      #outer {
        width: 200px;
        height: 200px;
        background: lightgreen;
      }

      #inner {
        width: 100px;
        height: 100px;
        background: lightcoral;
      }
    style>
  head>

  <body>
    <div id="root" class="center">#root
      <div id="outer" class="center">#outer
        <div id="inner" class="center">#innerdiv>
      div>
    div>
  body>
html>

<script>
  const html = document.documentElement;
  const body = document.body;
  const root = document.querySelector("#root");
  const outer = document.querySelector("#outer");
  const inner = document.querySelector("#inner");

  // 手动做的事件绑定
  window.addEventListener("click",() => {console.log(`捕获-window`);},true);
  window.addEventListener("click",() => {console.log(`冒泡-window`);},false);

  document.addEventListener("click",() => {console.log(`捕获-document`);},true);
  document.addEventListener("click",() => {console.log(`冒泡-document`);},false);

  html.addEventListener("click",() => {console.log(`捕获-html`);},true);
  html.addEventListener("click",() => {console.log(`冒泡-html`);},false);

  body.addEventListener("click",() => {console.log(`捕获-body`);},true);
  body.addEventListener("click",() => {console.log(`冒泡-body`);},false);

  root.addEventListener("click",() => {console.log(`捕获-root`);},true);
  root.addEventListener("click",() => {console.log(`冒泡-root`);},false);

  outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
  outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);

  inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
  inner.addEventListener("click",() => {console.log(`冒泡-inner`);},false);
script>

事件传播

  • 示例:

    • 结构示例:

      DOCTYPE html>
      <html>
        <body>
          <div id="root" class="center">#root
            <div id="outer" class="center">#outer
              <div id="inner" class="center">#innerdiv>
            div>
          div>
        body>
      html>
      
      <script>
        const html = document.documentElement;
        const body = document.body;
        const root = document.querySelector("#root");
        const outer = document.querySelector("#outer");
        const inner = document.querySelector("#inner");
      
        // 手动做的事件绑定
        window.addEventListener("click",() => {console.log(`捕获-window`);},true);
        window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
      
        document.addEventListener("click",() => {console.log(`捕获-document`);},true);
        document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
      
        html.addEventListener("click",() => {console.log(`捕获-html`);},true);
        html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
      
        body.addEventListener("click",() => {console.log(`捕获-body`);},true);
        body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
      
        root.addEventListener("click",() => {console.log(`捕获-root`);},true);
        root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
      
        outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
        outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
      
        inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
        inner.addEventListener("click",() => {console.log(`冒泡-inner`);},false);
      script>
      
    • 执行示例:

      • 点击#inner后:

        DOCTYPE html>
        <html>
          <body>
            <div id="root" class="center">#root
              <div id="outer" class="center">#outer
                <div id="inner" class="center">#innerdiv>
              div>
            div>
          body>
        html>
        
        <script>
          const html = document.documentElement;
          const body = document.body;
          const root = document.querySelector("#root");
          const outer = document.querySelector("#outer");
          const inner = document.querySelector("#inner");
        
          // 执行顺序:
          window.addEventListener("click",() => {console.log(`捕获-window`);},true);
          document.addEventListener("click",() => {console.log(`捕获-document`);},true);
          html.addEventListener("click",() => {console.log(`捕获-html`);},true);
          body.addEventListener("click",() => {console.log(`捕获-body`);},true);
          root.addEventListener("click",() => {console.log(`捕获-root`);},true);
          outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
          inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
        
          inner.addEventListener("click",() => {console.log(`冒泡-inner`);},false);
          outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
          root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
          body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
          html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
          document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
          window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
        script>
        
      • 点击#outer后:

        DOCTYPE html>
        <html>
          <body>
            <div id="root" class="center">#root
              <div id="outer" class="center">#outer
                <div id="inner" class="center">#innerdiv>
              div>
            div>
          body>
        html>
        
        <script>
          const html = document.documentElement;
          const body = document.body;
          const root = document.querySelector("#root");
          const outer = document.querySelector("#outer");
          const inner = document.querySelector("#inner");
        
          // 执行顺序:
          window.addEventListener("click",() => {console.log(`捕获-window`);},true);
          document.addEventListener("click",() => {console.log(`捕获-document`);},true);
          html.addEventListener("click",() => {console.log(`捕获-html`);},true);
          body.addEventListener("click",() => {console.log(`捕获-body`);},true);
          root.addEventListener("click",() => {console.log(`捕获-root`);},true);
          outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
          
          outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
          root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
          body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
          html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
          document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
          window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
        script>
        
      • 点击#root后:

        DOCTYPE html>
        <html>
          <body>
            <div id="root" class="center">#root
              <div id="outer" class="center">#outer
                <div id="inner" class="center">#innerdiv>
              div>
            div>
          body>
        html>
        
        <script>
          const html = document.documentElement;
          const body = document.body;
          const root = document.querySelector("#root");
          const outer = document.querySelector("#outer");
          const inner = document.querySelector("#inner");
        
          // 执行顺序:
          window.addEventListener("click",() => {console.log(`捕获-window`);},true);
          document.addEventListener("click",() => {console.log(`捕获-document`);},true);
          html.addEventListener("click",() => {console.log(`捕获-html`);},true);
          body.addEventListener("click",() => {console.log(`捕获-body`);},true);
          root.addEventListener("click",() => {console.log(`捕获-root`);},true);
          
          root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
          body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
          html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
          document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
          window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
        script>
        
  • 事件传播的特点:

    1. 事件传播中的捕获阶段从window到document到html到body,直到事件源的祖先元素。

    2. 事件源的捕获与传播就是目标阶段。

    3. 事件传播中的捕获阶段从事件源的祖先元素到body,再到html到document到window。

    4. 一个DOM元素在事件传播的过程中的每一步都可以绑定多个事件。

      • 都会执行。
      <script>
        const inner = document.querySelector("#inner");
      
        inner.addEventListener("click",() => {console.log(`冒泡-inner-1`);},false);
        inner.addEventListener("click",() => {console.log(`冒泡-inner-2`);},false);
        inner.addEventListener("click",() => {console.log(`冒泡-inner`);},false);
      script>
      
      <script>
        const html = document.documentElement;
        const body = document.body;
        const root = document.querySelector("#root");
        const outer = document.querySelector("#outer");
        const inner = document.querySelector("#inner");
      
        // 执行顺序:
        window.addEventListener("click",() => {console.log(`捕获-window`);},true);
        document.addEventListener("click",() => {console.log(`捕获-document`);},true);
        html.addEventListener("click",() => {console.log(`捕获-html`);},true);
        body.addEventListener("click",() => {console.log(`捕获-body`);},true);
        root.addEventListener("click",() => {console.log(`捕获-root`);},true);
        outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
        inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
      
        inner.addEventListener("click",() => {console.log(`冒泡-inner-1`);},false);
        inner.addEventListener("click",() => {console.log(`冒泡-inner-2`);},false);
        outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
        root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
        body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
        html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
        document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
        window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
      script>
      
    5. event.stopPropagation():只会阻止下一步的处理,但是对于当前这一步的当前所在DOM元素绑定的多个方法依然都会被触发执行。

      • 阻止事件的传播,包括所有阶段:如冒泡阶段与捕获阶段都会被阻止。
      <script>
        const html = document.documentElement;
        const body = document.body;
        const root = document.querySelector("#root");
        const outer = document.querySelector("#outer");
        const inner = document.querySelector("#inner");
      
        // 执行顺序:
        window.addEventListener("click",() => {console.log(`捕获-window`);},true);
        document.addEventListener("click",() => {console.log(`捕获-document`);},true);
        html.addEventListener("click",() => {console.log(`捕获-html`);},true);
        body.addEventListener("click",() => {console.log(`捕获-body`);},true);
        root.addEventListener("click",() => {console.log(`捕获-root`);},true);
        outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
        inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
      
        inner.addEventListener("click",(event) => {event.stopPropagation();console.log(`冒泡-inner-1`);},false);
        inner.addEventListener("click",() => {console.log(`冒泡-inner-2`);},false);
        outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
        root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
        body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
        html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
        document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
        window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
      script>
      
      • 冒泡-inner-1冒泡-inner-2都会打印,但冒泡-outer及其后方都不会再执行了!
    6. event.stopImmediatePropagation():不仅会阻止下一步的处理,对于当前这一步的当前所在DOM元素上绑定的其它方法,如果还没有执行,也不会再执行了!

      • 阻止事件的传播,包括所有阶段:如冒泡阶段与捕获阶段都会被阻止。
      <script>
        const html = document.documentElement;
        const body = document.body;
        const root = document.querySelector("#root");
        const outer = document.querySelector("#outer");
        const inner = document.querySelector("#inner");
      
        // 执行顺序:
        window.addEventListener("click",() => {console.log(`捕获-window`);},true);
        document.addEventListener("click",() => {console.log(`捕获-document`);},true);
        html.addEventListener("click",() => {console.log(`捕获-html`);},true);
        body.addEventListener("click",() => {console.log(`捕获-body`);},true);
        root.addEventListener("click",() => {console.log(`捕获-root`);},true);
        outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
        inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
      
        inner.addEventListener("click",(event) => {event.stopImmediatePropagation();console.log(`冒泡-inner-1`);},false);
        inner.addEventListener("click",() => {console.log(`冒泡-inner-2`);},false);
        outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
        root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
        body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
        html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
        document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
        window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
      script>
      
      • 冒泡-inner-1会打印,但冒泡-inner-2冒泡-outer及其后方都不会再执行了!

事件委托

  • 事件例子:

    DOCTYPE html>
    <html>
      <body>
        <div id="root" class="center">#root
          <div id="outer" class="center">#outer
            <div id="inner" class="center">#innerdiv>
          div>
        div>
      body>
    html>
    <script>
      // 点击body中的任意元素,都会触发body的点击事件。
      document.body.addEventListener("click", function (ev) {
        let target = ev.target;
        let targetTag = target.tagName;
        let targetId = target.is;
    
        if(targetTag==='BODY'){
          console.log(`body`);
          return
        }
        if(targetId==='root'){
          console.log(`root`);
          return
        }
        if(targetId==='outer'){
          console.log(`outer`);
          return
        }
    
        console.log(`点击的是非预期元素`);
      },false);
    
      // 其它的处理逻辑比较复杂,可以单独绑定。
      document.querySelector(`#inner`).addEventListener('click',function(ev){
        console.log(`inner,这一行执行的代码比较多。`);
        ev.stopPropagation()
      },false)
    script>
    
  • 事件委托,也被叫做事件代理。

    • 事件委托就是利用事件的传播机制来实现的一种项目解决方案

    • 例如:一个容器中有很多后代元素,其中大部分后代元素,在点击的时候,都会处理一些事情。有些元素被点击时做相同的事情,有些元素点击时做不同的事情。

      1. 传统方案:想操作那些元素,就把这些元素全部都获取到,然后逐一做事件绑定。

        DOCTYPE html>
        <html>
          <body>
            <div id="root" class="center">#root
              <div id="outer" class="center">#outer
                <div id="inner" class="center">#innerdiv>
              div>
            div>
          body>
        html>
        <script>
          document.querySelector(`body`).addEventListener("click", function (ev) {
              console.log(`body`);
          },false);
        
          document.querySelector(`#root`).addEventListener("click", function (ev) {
              console.log(`root`);
            if(targetId==='outer'){
              console.log(`outer`);
              return
            }
        
            console.log(`点击的是非预期元素`);
          },false);
        
          document.querySelector(`#outer`).addEventListener("click", function (ev) {
              console.log(`outer`);
          },false);
        
          document.querySelector(`#inner`).addEventListener('click',function(ev){
            console.log(`inner,这一行执行的代码比较多。`);
            ev.stopPropagation()
          },false)
        script>
        
        • 这种方案不仅操作起来复杂,并且会开辟很多堆内存,性能也不是很好。
      2. 新方案:利用事件的传播机制,不逐一获取元素和单独绑定事件了,而是只给外层容器做一个事件绑定,这样不管点击其后代中的那一个元素,当前容器的点击事件也会触发,把容器元素上绑定的方法执行;在容器元素所绑定的方法中,我们只需要判断事件源是谁,从而处理不同的事件即可!===>事件委托。

        • 事件对象就是事件函数的第一个形参,一般用e或ev或event来接收。
        • 事件源通过事件对象的target属性来获取。
        • 判断事件源不一定要判断到具体的某个元素,而是该元素有一些共同特点,如元素标签为某一类型或者该元素为某个类,或者该元素有某个特定属性。
          • 事件对象.target 事件源。
          • 事件对象.target.targetTag 事件源的大写形式的标签类型。
          • 事件对象.target.class 事件源的字符串形式类名。
          • 事件对象.target.classList 事件源的数组形式类名。
          • 事件对象.target.id 事件源的id属性。
        • 如果其中某个元素点击的时候,要干的事情比较复杂,不想和事件委托的代码混在一起,我们可以单独为这个元素做事件绑定。
        DOCTYPE html>
        <html>
          <body>
            <div id="root" class="center">#root
              <div id="outer" class="center">#outer
                <div id="inner" class="center">#innerdiv>
              div>
            div>
          body>
        html>
        <script>
          // 点击body中的任意元素,都会触发body的点击事件。
          document.body.addEventListener("click", function (ev) {
            let target = ev.target;
            let targetTag = target.tagName;
            let targetId = target.id;
        
            if(targetTag==='BODY'){
              console.log(`body`);
              return
            }
            if(targetId==='root'){
              console.log(`root`);
              return
            }
            if(targetId==='outer'){
              console.log(`outer`);
              return
            }
        
            console.log(`点击的是非预期元素`);
          },false);
        
          // 其它的处理逻辑比较复杂,可以单独绑定。
          document.querySelector(`#inner`).addEventListener('click',function(ev){
            console.log(`inner,这一行执行的代码比较多。`);
            ev.stopPropagation()
          },false)
        script>
        
        • 这种新方案的性能比传统方案提高40%以上!
    • 例如:我们容器中的元素不是写死的,而是动态绑定(动态创建)的,而且是会持续动态创建。我们需要在点击每个元素的时候,做一些事情!

      1. 传统方案:每一次动态创建完容器中的新创建元素,都需要获取新创建元素,给这些新创建元素单独做事件绑定!
      2. 事件委托:只需要给容器的点击事件绑定方法,无论其内部元素是写死的,还是动态绑定的,只要点击了,都说明元素已经存在了,基于事件传播机制,我们只需要在外层容器中判断事件源,做不同的事情即可!
        • 判断事件源,可以通过事件源的类名或标签名。就执行一个特定的方法或操作。
        • 这也就说明事件委托可以给动态绑定的元素做事件绑定!
      • 例子:
        • 原DOM结构:

          DOCTYPE html>
          <html>
            <body>
              <div id="root" class="center">#root
                <div id="outer" class="center">#outer
                div>
              div>
            body>
          html>
          
        • 后续变成的DOM结构:在#outer添加了#inner。

          DOCTYPE html>
          <html>
            <body>
              <div id="root" class="center">#root
                <div id="outer" class="center">#outer
                  <div id="inner" class="center">#innerdiv>
                div>
              div>
            body>
          html>
          
    • 真实项目中,还有很多需求,必须基于事件委托来做。

      • 比如说:
        1. 点击容器中非某个元素,该元素就隐藏!
      • 所以:以后但凡再遇到事件绑定的需求,要首先想到是否有必要基于事件委托处理,如果确定有必要,则直接基于事件委托来处理!

React中的合成事件

import React from "react";
import "./DemoEvent.less";

export default class Demo extends React.Component {
  render() {
    return (
      
{ console.log("outer 冒泡「合成」"); }} onClickCapture={() => { console.log("outer 捕获「合成」"); }} >
{ console.log("inner 冒泡「合成」"); }} onClickCapture={() => { console.log("inner 捕获「合成」"); }} >
); } componentDidMount() { // 原生事件的绑定。 document.addEventListener("click",() => console.log("document 捕获"),true); document.addEventListener("click",() => console.log("document 冒泡"),false); document.body.addEventListener("click",() => console.log("body 捕获"),true); document.body.addEventListener("click",() => console.log("body 冒泡"),false); let root = document.querySelector("#root"); root.addEventListener("click", () => console.log("root 捕获"), true); root.addEventListener("click", () => console.log("root 冒泡"), false); let outer = document.querySelector(".outer"); outer.addEventListener("click", () => console.log("outer 捕获"), true); outer.addEventListener("click", () => console.log("outer 冒泡"), false); let inner = document.querySelector(".inner"); inner.addEventListener("click", () => console.log("inner 捕获"), true); inner.addEventListener("click", () => console.log("inner 冒泡"), false); } }
  • React中的合成事件SyntheticEvent

    1. 什么是合成事件?
      • 合成事件是围绕浏览器原生事件,充当跨浏览器包装器的对象;它们将不同浏览器的行为合并为一个API,这样做是为了确保事件在不同浏览器中显示一致的属性

      • 例如:

        {}} onClickCapture={()=>{}} >
        • 这种在jsx元素上,基于onXxx绑定的事件,都是合成事件,就是React内部对这些事件操作,进行过特殊的处理
  • React18中的合成事件绑定原理-事件委托:

    const html = document.documentElement;
    const body = document.body;
    const root = document.querySelector("#root");
    const outer = document.querySelector("#outer");
    const inner = document.querySelector("#inner");
    
    // React18合成事件原理。
    
    const handlerSyntheticEv = function handlerSyntheticEv(ev){
      // 
      // ...
      return ev
    }
    root.addEventListener("click",function (ev) {
      //获取传播路径-默认是从内到外。
      // console.log(`ev.path-->`, ev.path);
      // console.log(`ev.composedPath()-->`, ev.composedPath());
      let path = ev.composedPath();
      let syntheticBaseEvent = handlerSyntheticEv(ev)
      path.reverse().forEach((elem) => {
        if (!elem.hasOwnProperty("onClickCaptrue")) {
          return;
        }
        elem.onClickCaptrue(syntheticBaseEvent);
      });
    },true);
    root.addEventListener("click",function (ev) {
      //获取传播路径-默认是从内到外。
      let path = ev.composedPath();
      let syntheticBaseEvent = handlerSyntheticEv(ev)
      path.forEach((elem) => {
        if (!elem.hasOwnProperty("onClick")) {
          return;
        }
        elem.onClick(syntheticBaseEvent);
      });
    },false);
    
    // jsx渲染:
    outer.onClick=() => {console.log("outer 冒泡「合成」");}
    outer.onClickCapture=() => {console.log("outer 捕获「合成」");}
    inner.onClick=() => {console.log("inner 冒泡「合成」");}
    inner.onClickCapture=() => {console.log("inner 捕获「合成」");}
    
    • DemoEvent.less

      .center {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
      
      .outer {
        .center;
        width: 200px;
        height: 200px;
        background: lightblue;
      
        .inner {
          .center;
          width: 100px;
          height: 100px;
          background: lightcoral;
        }
      }
      
    • DemoEvent.jsx

      import React from "react"
      import './DemoEvent.less'
      
      export default class Demo extends React.Component {
        render() {
          return 
      { console.log('outer 冒泡「合成」') }} onClickCapture={() => { console.log('outer 捕获「合成」') }}>
      { // ev 合成事件对象 // ev.nativeEvent 原生事件对象 // 合成事件对象的阻止事件传播:阻止合成事件的传播以及原生事件的传播 // ev.stopPropagation() // 原生事件对象的阻止事件传播:只能阻止原生的事件传播 // ev.nativeEvent.stopImmediatePropagation() console.log('inner 冒泡「合成」', ev) }} onClickCapture={() => { console.log('inner 捕获「合成」') }}>
      } componentDidMount() { document.addEventListener('click', () => console.log('document 捕获'), true) document.addEventListener('click', () => console.log('document 冒泡'), false) document.body.addEventListener('click', () => console.log('body 捕获'), true) document.body.addEventListener('click', () => console.log('body 冒泡'), false) let root = document.querySelector('#root') root.addEventListener('click', () => console.log('root 捕获'), true) root.addEventListener('click', () => console.log('root 冒泡'), false) let outer = document.querySelector('.outer') outer.addEventListener('click', () => console.log('outer 捕获'), true) outer.addEventListener('click', () => console.log('outer 冒泡'), false) let inner = document.querySelector('.inner') inner.addEventListener('click', () => console.log('inner 捕获'), true) inner.addEventListener('click', (ev) => { // ev 原生事件对象 // ev.stopImmediatePropagation() console.log('inner 冒泡') }, false) } }
    1. 在jsx视图编译的时候,当遇到某个元素上出现onXxx合成事件绑定,其实并没有给元素做事件绑定,只是给这个元素设置了一个onXxx的私有属性,属性值就是我们绑定的方法。

      • jsx代码:

        {console.log("inner 冒泡「合成」"); }} onClickCapture={() => {console.log("inner 捕获「合成」");}} >
      • 渲染的结果:

        inner.onClick=函数
        inner.onClickCapture=函数
        
        inner.onClick={() => {console.log("inner 冒泡「合成」"); }}
        inner.onClickCapture={() => {console.log("inner 捕获「合成」");}}
        
      • 没有做任何的浏览器事件绑定,仅仅是给inner设置了两个私有属性!

    2. 在React内部,会给#root容器的点击行为,做事件绑定,捕获和冒泡阶段都处理了!React所有的视图编译完毕后,都会插入到#root容器中,这样点击任何一个元素,都会把#root的点击行为触发!

      • 在React内部,默认对大部分浏览器标准事件都进行了这样的委托处理。
        • 这些做了委托处理的浏览器标准事件的前提:它们是支持事件传播机制的标准事件。
      • 在React内部,对于不支持事件传播机制的事件,在jsx渲染的时候,就单独给元素做了相关的事件绑定!
      • jsx代码:
      root.addEventListener("Xxx", function(){
        //获取传播路径。
        //按照从外到内的顺序,把元素的onXxxCapture合成事件的私有属性,依次触发执行。
      }, true);
      root.addEventListener("Xxx", function(){
        //获取传播路径。
        //按照从内到外的顺序,把元素的onXxx合成事件的私有属性,依次触发执行。
      }, false);
      
    
    
      
        
        
        
        事件委托
        
        
      
    
      
        
  • React中的合成事件对象:

    • 合成事件对象的阻止事件传播函数:阻止合成事件的传播以及原生事件的传播。
      • 合成事件对象没有stopImmediatePropagation()。
    • 原生事件对象的阻止事件传播:只能阻止原生的事件传播。
  • React16中,其合成事件的底层处理机制和React18有很大的区别:

    1. 首先绑定的合成事件,对于有传播机制的标准事件来讲,也不是做的事件绑定,而是变为元素的onXxx/onXxxCapture私有属性!
    2. 只不过React16不是给#root做事件委托,而是委托给了document,并且只做了冒泡阶段的委托。
      • 即合成事件的捕获与冒泡都是在事件冒泡到document时才执行的。
      1. 获取传播路径-默认是从内到外。
      2. 处理合成事件对象;
        • 这里和React16中的合成事件对象与React中的合成事件对象不一样。
          • React16中的合成对象是做了get与set劫持。
      3. 把传播路径按照倒序,依次执行元素.onXxxCapture()方法;
      4. 把传播路径按照正序,依次执行元素.onXxx()方法;
      const html = document.documentElement;
      const body = document.body;
      const root = document.querySelector("#root");
      const outer = document.querySelector("#outer");
      const inner = document.querySelector("#inner");
    
      // React16合成事件原理。
      const handlerSyntheticEv = function handlerSyntheticEv(ev){
        // 
        // ...
        return ev
      }
      document.addEventListener("click",function (ev) {
        //获取传播路径-默认是从内到外。
        //处理合成事件对象;
        //把传播路径按照倒序,依次执行元素.onXxxCapture()方法;
        //把传播路径按照正序,依次执行元素.onXxx()方法;
        // console.log(`ev.composedPath()-->`, ev.composedPath());
      },false);
    
      // jsx渲染:
      outer.onClick=() => {console.log("outer 冒泡「合成」");}
      outer.onClickCapture=() => {console.log("outer 捕获「合成」");}
      inner.onClick=() => {console.log("inner 冒泡「合成」");}
      inner.onClickCapture=() => {console.log("inner 捕获「合成」");}
    
    • DemoEvent.less

      .center {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
      
      .outer {
        .center;
        width: 200px;
        height: 200px;
        background: lightblue;
      
        .inner {
          .center;
          width: 100px;
          height: 100px;
          background: lightcoral;
        }
      }
      
    • DemoEvent.jsx

      import React from "react"
      import './DemoEvent.less'
      
      export default class Demo extends React.Component {
        render() {
          return 
      { console.log('outer 冒泡「合成」') }} onClickCapture={() => { console.log('outer 捕获「合成」') }}>
      { // ev 合成事件对象 // ev.nativeEvent 原生事件对象 // 合成事件对象的阻止事件传播:阻止合成事件的传播以及原生事件的传播 // ev.stopPropagation() // 原生事件对象的阻止事件传播:只能阻止原生的事件传播 // ev.nativeEvent.stopImmediatePropagation() console.log('inner 冒泡「合成」', ev) }} onClickCapture={() => { console.log('inner 捕获「合成」') }}>
      } componentDidMount() { document.addEventListener('click', () => console.log('document 捕获'), true) document.addEventListener('click', () => console.log('document 冒泡'), false) document.body.addEventListener('click', () => console.log('body 捕获'), true) document.body.addEventListener('click', () => console.log('body 冒泡'), false) let root = document.querySelector('#root') root.addEventListener('click', () => console.log('root 捕获'), true) root.addEventListener('click', () => console.log('root 冒泡'), false) let outer = document.querySelector('.outer') outer.addEventListener('click', () => console.log('outer 捕获'), true) outer.addEventListener('click', () => console.log('outer 冒泡'), false) let inner = document.querySelector('.inner') inner.addEventListener('click', () => console.log('inner 捕获'), true) inner.addEventListener('click', (ev) => { // ev 原生事件对象 // ev.stopImmediatePropagation() console.log('inner 冒泡') }, false) } }
  • React16中除了合成事件底层处理机制和React18不同,对于合成事件对象的处理,和React18也是不一样的!

    • 在React16版本中,存在事件对象池,用的合成事件对象只有一个,每一次事件触发,都是把合成事件对象的值,改为最新的值!但是一旦用完,则把这些值又释放掉了!
      • React16中的合成对象是做了get与set劫持。
    • 在React18版本中,取消了事件对象池机制,每一次事件触发都是创建新的合成事件对象,之前创建的合成事件对象,在没有用处的情况下,会被销毁掉!

React中的样式私有化

  • 在React项目中,我们在.css/.less/.sass等文件中编写的样式,默认都是全局样式
    • 这样在组件合并渲染的时候,很可能导致各组件之间编写的样式发生冲突。
      • 因为不同组件之间的类名可能由不同的人来写,类名就可能会起一样了。
      • 所以我们期望:在各组件中写的样式,可以只对当前组件起作用
        • 这也就是组件样式的私有化处理方案。
    • 例子:
      • Demo1.jsx

        import React from "react";
        import './Demo1.less'
        export default class Demo1 extends React.Component {
          render() {
            return (
              

        组件1 span

        ); } }
      • Demo1.less

        .demo {
          background: skyblue;
          .title {
            font-size: 18px;
            color: red;
          }
        }
        
      • Demo2.jsx

        import React from "react"
        import './Demo2.less'
        export default class Demo2 extends React.Component {
          render() {
            return 

        组件2

        } }
      • Demo2.less

        .demo {
          background: skyblue;
          .title {
            font-size: 18px;
            color: red;
          }
        }
        
      • Demo1.jsxDemo2.jsx中的样式用的都是全局的样式。具体那个生效,就看那个的权重及所处的前后位置了。

  • 样式私有化处理方案:
    1. 样式私有化处理方案一:人为有意识地、有规范地规避冲突问题。

      • 这种是主流的蛮常用方案。
      1. 使用lesssassstylus等预编译语言,主要是是利用其提供的嵌套功能。

      2. 制定组件命名规范,尤其是组件根节点样式的命名规范,保证所有组件复用多次的情况当前组件根节点的样式类名是唯一的。

        • 例如:
          • 使用类似于组件所在的文件夹路径-组件名-box的命名规范。

            • /src/components/TabBar.jsx 根节点的样式类名为common-tabbar-box;
            • /views/home/TableList.jsx 根节点的样式类名为home-table-list-box;
            • /views/device/Login.jsx 根节点的样式类名为device-table-list-box;
            • /views/Login.jsx 根节点的样式类名为login-box;
            //假设目录为:
            |-components
              |-TabBar.jsx  ->  common-tabbar-box
              |-...
            |-views
              |-home
                |-TableList.jsx  ->  home-table-list-box
                |-...
              |-device
                |-TableList.jsx  ->  device-table-list-box
                |-...
              |-Login.jsx   ->  login-box
              |-...
            
      3. 而组件内部所有元素的样式,都要保证在该组件的样式类名下编写。

        .device-table-list-box{
          .title{ 
            ... 
            .num{ ... }
          }
          span{ ... }
        }
        
      • 存在的弊端:
        • 如果选择器有过长的前缀,会影响渲染的性能。

          //从右到左渲染
          .box a{}//先找到所有的a标签,再筛选出哪些在 .box 下。
          a{}//找到所有的a标签。所以这个选择器的性能好。
          
          • 如果是多级,那么就要查找多层,会导致筛选过程浪费大量性能。
          • 因为这个是类似于DOM树的结构来写的css树,所以导致从其末尾一步一步向主干查询,会有很多的筛选流程及无效筛选。比如筛选着筛选着,发现没一个DOM元素符合该选择器。
        • 在样式表中编写的样式都是静态样式,无法基于js等业务逻辑控制其动态化。

          • 这个是所有css与js与html分离都有的问题,不用太过在意。
      • 例子:
        • Demo1.jsx

          import React from "react";
          import './Demo1.less'
          export default class Demo1 extends React.Component {
            render() {
              return (
                

          组件1 span

          ); } }
        • Demo1.less

          .views-demo1-box {
            background: skyblue;
            .title {
              font-size: 18px;
              color: red;
            }
          }
          
        • Demo2.jsx

          import React from "react"
          import './Demo2.less'
          export default class Demo2 extends React.Component {
            render() {
              return 

          组件2

          } }
        • Demo2.less

          .views-demo2-box {
            background: skyblue;
            .title {
              font-size: 18px;
              color: red;
            }
          }
          
    2. 样式私有化处理方案二:CSS Modules

      • 这是一种基于技术手段,保证编写样式类名的唯一性
      1. 样式表的命名:xxx.module.cssxxx.module.less。如果不使用less后缀与scss后缀等,可以不再使用less/sass等预编译语言了。

      2. 在webpack编译的时候,会把.module.css中所有的样式类名,都进行编译处理,按照规则变为唯一的新式类。

        • 例如:在Demo1.module.css文件中:

          .demo {...}
          .title {...}
          .title span {...}
          
        • 经过编译后的样式:

          .Demo1_demo__bLprq {...}
          .Demo1_title__EpCHF {...}
          .Demo1_title__EpCHF span {...}
          
      3. 在组件中使用的时候:

        import sty from './Demo1.module.css'
        /* sty = {
            demo: "Demo1_demo__bLprq",
            title: "Demo1_title__EpCHF"
        } */
        

    3. 样式私有化处理方案三:基于技术手段,把css写在js中。这样的处理思想,我们称之为css in js;

      • 依赖第三方插件来进行处理:
        • React-JSS
        • styled-components,常用的。
        • …。
    4. 所有样式都写成内联样式行内样式。

      • 这种不太常见,难维护,比较少见。
      • 不用偶尔小范围也有使用,比如做动态样式时。

React-JSS的使用

styled-components的使用

  • 安装vscode插件vscode-styled-components

  • 使用步骤:

    • 导入插件,基于插件编写样式「相当于创建一个样式组件,其内部包含的是需要的样式」

        import styled from "styled-components"
        const colorRed = 'red'
        const DemoBox = styled.div`
            ...
            .title{
                ...,
                color: ${colorRed};
            }
        `
      
    • 视图中,直接基于创建的组件来渲染样式即可

        export default class Demo1 extends React.Component {
            render() {
                return 
                    

      ...

      } }
  • 编译的原理:在渲染DemoBox组件的时候,会被渲染为一个div标签,并且给其设置一个唯一的样式类名。

    • 而我们写的样式,最后也会编译为正常的CSS样式,不过都是在 “sc-bcXHqe bscqLV” 唯一的样式类下编写的!
  • 基于css-in-js

    • 一方面基于技术手段保证了样式的私有化
    • 另一方面,因为CSS样式是写在JS中的,我们就可以根据业务逻辑,动态控制其渲染的样式
  • 实战开发中的细节问题:

    • 公共样式的设置。

    • 公共样式优先级。

      • 如果冲突了,想让谁生效,就提高谁的优先级。
      • 在多个“样式组件”嵌套的情况下,如果对相同元素都设置了样式,最后以谁的样式为主,就看优先级,如果想指定以谁为主,也是提高其优先级(!important)
    • 动态样式:

css性能问题

//从右到左渲染
.box a{}//先找到所有的a标签,再筛选出哪些在 .box 下。
a{}//找到所有的a标签。所以这个选择器的性能好。

进阶参考

你可能感兴趣的:(重返学习,react.js,React,react.js,学习,javascript)