react 原理揭秘

news2024/11/17 23:54:48

1.目标

A. 能够知道setState()更新数据是异步的
B. 能够知道JSX语法的转化过程
C. 能够说出React组件的更新机制
D. 能够对组件进行性能优化
E. 能够说出虚拟DOM和Diff算法

2.目录

A. setState()的说明
B. JSX语法的转化过程
C. 组件更新机制
D. 组件性能优化
E. 虚拟DOM和Diff算法

3.setState()的说明

3.1 更新数据

A. setState() 是异步更新数据的
B. 注意:使用该语法时,后面的setState()不能依赖于前面的setState()
C. 可以多次调用setState(),只会触发一次重新渲染
1setState.js

import React from "react";

class App31 extends React.Component {
  state = {
    count: 0,
  };
  handleClick = () => {
    //异步更新操作
    this.setState({
      count: this.state.count + 1,
    });
    console.log("count1:" + this.state.count);
    this.setState({
      count: this.state.count + 1,
    });
    console.log("count2:" + this.state.count);
  };
  render() {
    //数据更新了就会调用一次render
    //但是,如果数据变化一样的,render只调用一次
    console.log("render");
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>点击</button>
      </div>
    );
  }
}

export default App31;

index,js

import App31 from "./1setState";
ReactDOM.createRoot(document.getElementById("root")).render(<App31></App31>);

3.2 推荐语法

A. 推荐:使用setState((state,props)=>{})语法
B. 参数state:表示最新的state
C. 参数props:表示最新的props

import React from "react";

class App31 extends React.Component {
  state = {
    count: 1,
  };
  handleClick = () => {
    // //异步更新操作
    // this.setState({
    //   count: this.state.count + 1,
    // });
    // console.log("count1:" + this.state.count);
    // this.setState({
    //   count: this.state.count + 1,
    // });
    // console.log("count2:" + this.state.count);

    //推荐语法
    //注意:这种语法也是异步更新state的,但是会记录最新的state的数据,所以在页面显示为3
    this.setState((state, props) => {
      console.log("state1:", state, "props1:", props);
      return {
        count: state.count + 1,
      };
    });
    console.log("count:", this.state.count); // 1

    this.setState((state, props) => {
      console.log("state2:", state, "props2:", props);
      return {
        count: state.count + 1,
      };
    });
    console.log("count:", this.state.count); // 1
  };
  render() {
    //数据更新了就会调用一次render
    //但是,如果数据变化一样的,render只调用一次
    console.log("render");
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>点击</button>
      </div>
    );
  }
}

export default App31;

3.3 第二个参数

A. 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
B. 语法:setState(update [,callback])

import React from "react";

class App31 extends React.Component {
  state = {
    count: 1,
  };
  handleClick = () => {
    // //异步更新操作
    // this.setState({
    //   count: this.state.count + 1,
    // });
    // console.log("count1:" + this.state.count);
    // this.setState({
    //   count: this.state.count + 1,
    // });
    // console.log("count2:" + this.state.count);
    //推荐语法
    //注意:这种语法也是异步更新state的,但是会记录最新的state的数据,所以在页面显示为3
    // this.setState((state, props) => {
    //   console.log("state1:", state, "props1:", props);
    //   return {
    //     count: state.count + 1,
    //   };
    // });
    // console.log("count:", this.state.count); // 1
    // this.setState((state, props) => {
    //   console.log("state2:", state, "props2:", props);
    //   return {
    //     count: state.count + 1,
    //   };
    // });
    // console.log("count:", this.state.count); // 1

    // 第二个参数,在状态更新(页面完成重新渲染)后立即执行某个操作
    this.setState(
      (state, props) => {
        return {
          count: state.count + 1,
        };
      },
      () => {
        console.log("状态更新完成,当前count值:", this.state.count);
      }
    );
  };
  render() {
    //数据更新了就会调用一次render
    //但是,如果数据变化一样的,render只调用一次
    console.log("render");
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>点击</button>
      </div>
    );
  }
}

export default App31;

4.JSX语法的转化过程

A. JSX仅仅是createElement()方法的语法糖(简化语法)
B. JSX语法被@babel/preset-react插件编译为createElement()方法
C. React元素:是一个对象,用来描述你希望的屏幕上看到的内容
在这里插入图片描述

const element=<h1 className='greeting'>Hello JSX!</h1>
// const element1=React.createElement('h1',{
// className:'greeting'
// },'Hello JSX!!!')
console.log(element);
// console.log(element1);
ReactDOM.render(element,document.getElementById('root'))

效果图:
在这里插入图片描述

5.组件更新机制

A. setState()的两个作用:1.修改state 2.更新组件(UI)
B. 过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)
在这里插入图片描述
首次加载时渲染
在这里插入图片描述
点击根组件会触发所有组件,点击左侧父组件1时会触发局部更新,只更新当前组件与子组件,不会触发父组件
在这里插入图片描述
2comUpdate.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./comUpdate.css";

class App50 extends React.Component {
  state = {
    bgColor: "#369",
  };
  getBgColor = () => {
    return Math.floor(Math.random() * 256);
  };
  toggleBgColor = () => {
    this.setState({
      bgColor: `rgb(${this.getBgColor()},${this.getBgColor()},${this.getBgColor()})`,
    });
  };
  render() {
    console.log("根组件");
    return (
      <div
        className="rootParent"
        style={{ backgroundColor: this.state.bgColor }}
      >
        根组件
        <button onClick={this.toggleBgColor}>切换根组件颜色</button>
        <div className="app-wrapper">
          <Parent1></Parent1>
          <Parent2></Parent2>
        </div>
      </div>
    );
  }
}
// 左边
class Parent1 extends React.Component {
  state = {
    count: 0,
  };
  handleClick = (state) => {
    this.setState((state) => {
      return {
        count: state.count + 1,
      };
    });
  };
  render() {
    console.log("左侧父组件 1");
    return (
      <div className="Parent1">
        <span>左侧-父组件</span>
        <button onClick={this.handleClick}>点击({this.state.count}</button>
        <div className="parentWrapper1">
          <Child1></Child1>
          <Child2></Child2>
        </div>
      </div>
    );
  }
}
class Child1 extends React.Component {
  render() {
    console.log("左侧子组件 1-1");
    return <div className="child1">左侧子组件1-1</div>;
  }
}
class Child2 extends React.Component {
  render() {
    console.log("左侧子组件 1-2");
    return <div className="child2">左侧子组件1-2</div>;
  }
}

// 右边
class Parent2 extends React.Component {
  state = {
    count: 0,
  };
  handleClick = (state) => {
    this.setState((state) => {
      return {
        count: state.count + 2,
      };
    });
  };
  render() {
    console.log("右侧父组件 2");
    return (
      <div className="Parent1">
        <span>右侧-父组件</span>
        <button onClick={this.handleClick}>点击({this.state.count}</button>
        <div className="parentWrapper1">
          <Child3></Child3>
          <Child4></Child4>
        </div>
      </div>
    );
  }
}
class Child3 extends React.Component {
  render() {
    console.log("右侧子组件 2-1");
    return <div className="child1">右侧子组件2-1</div>;
  }
}
class Child4 extends React.Component {
  render() {
    console.log("右侧子组件 2-2");
    return <div className="child2">右侧子组件2-2</div>;
  }
}
export default App50;

index.js

import App50 from "./2comUpdate";
ReactDOM.createRoot(document.getElementById("root")).render(<App50></App50>);

comUpdate.css

.rootParent {
  width: 800px;
  height: 600px;
  margin: 0 auto;
  font-weight: 700;
  font-size: 22px;
}

.app-wrapper {
  display: flex;
  justify-content: space-between;
  margin-top: 20px;
}

.Parent1 {
  width: 40%;
  height: 400px;
  background-color: pink;
}

.parentWrapper1 {
  display: flex;
  justify-content: space-between;
  margin-top: 30px;
}

.child1 {
  width: 40%;
  height: 300px;
  background-color: lightgreen;
}

.child2 {
  width: 40%;
  height: 300px;
  background-color: lightgrey;
}

.Parent2 {
  width: 40%;
  height: 400px;
  background-color: salmon;
}

6.组件性能优化

6.1 减轻state

A. 减轻state:只存储跟组件渲染相关的数据(比如:count/列表数据/loading等)
B. 注意:不用做渲染的数据不要放在state中,比如定时器id等
C. 对于这种需要在多个方法中用到的数据,应该放在this中

6.2 避免不要的重新渲染

A. 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
B. 问题:子组件没有任何变化时也会重新渲染
C. 如何避免不必要的重新渲染吗?
D. 解决方案:使用钩子函数shouldComponentUpdate(nextProps,nextState)
E. 作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
F. 触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate->render)
3performanceOptimize.js

import React from "react";

class App62 extends React.Component {
  state = {
    count: 0,
  };
  handleClick = () => {
    this.setState((state) => {
      return {
        count: state.count + 1,
      };
    });
  };
  //钩子函数
  shouldComponentUpdate(nextProps, nextState) {
    //返回false,阻止组件重新渲染
    // return false;
    //最新的状态
    console.log("最新的state:", nextState);
    //更新前的状态
    console.log("this.state:", this.state);
    return true;
  }
  render() {
    console.log("组件更新了");
    return (
      <div>
        <h1>count:{this.state.count}</h1>
        <button onClick={this.handleClick}>点击</button>
      </div>
    );
  }
}

export default App62;

index.js

import App62 from "./3performanceOptimize";
ReactDOM.createRoot(document.getElementById("root")).render(<App62></App62>);

6.2.1 案例:随机数(nextState)

4App621Random.js

import React from "react";

class App621Random extends React.Component {
  state = {
    number: 0,
  };
  getRandom = () => {
    this.setState((state) => {
      return {
        number: Math.floor(Math.random() * 3),
      };
    });
  };
  //因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染
  shouldComponentUpdate(nextProps, nextState) {
    if (nextState.number === this.state.number) {
      return false;
    }
    return true;
  }
  render() {
    console.log("触发渲染");
    return (
      <div>
        <h1>随机数:{this.state.number}</h1>
        <button onClick={this.getRandom}>获取随机数</button>
      </div>
    );
  }
}

export default App621Random;

index.js

import App621Random from "./4App621Random";
ReactDOM.createRoot(document.getElementById("root")).render(
  <App621Random></App621Random>
);

6.2.2 案例:随机数(NextProps)

4App621Random.js

import React from "react";

class App621Random extends React.Component {
  state = {
    number: 0,
  };
  getRandom = () => {
    this.setState((state) => {
      return {
        number: Math.floor(Math.random() * 3),
      };
    });
  };
  //因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染
  // shouldComponentUpdate(nextProps, nextState) {
  //   if (nextState.number === this.state.number) {
  //     return false;
  //   }
  //   return true;
  // }
  render() {
    console.log("触发父组件的render");
    return (
      <div>
        {/* <h1>随机数:{this.state.number}</h1> */}
        <ChildNumber number={this.state.number}></ChildNumber>
        <button onClick={this.getRandom}>获取随机数</button>
      </div>
    );
  }
}
class ChildNumber extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    console.log("最新props:", nextProps, ",当前props:", this.props);
    return nextProps.number !== this.props.number;
  }
  render() {
    console.log("子组件中的render");
    return <h1>子组件的随机数:{this.props.number}</h1>;
  }
}

export default App621Random;

index.js

import App621Random from "./4App621Random";
ReactDOM.createRoot(document.getElementById("root")).render(
  <App621Random></App621Random>
);

6.3 纯组件

A. 纯组件:PureComponent与React.Component功能相似
B. 区别:PureComponent内部自动实现了shouldComponentUpdate钩子,无需手动比较
C. 原理:纯组件内部通过对比前后两次props和state的值,来决定是否重新渲染组件
5pure.js

import React from "react";

class PureApp extends React.PureComponent {
  state = {
    number: 0,
  };
  getRandom = () => {
    this.setState((state) => {
      return {
        number: Math.floor(Math.random() * 2),
      };
    });
  };
  render() {
    console.log("父组件render");
    return (
      <div>
        <h1>父组件随机数:{this.state.number}</h1>
        <ChildPure number={this.state.number}></ChildPure>
        <button onClick={this.getRandom}>获取随机数</button>
      </div>
    );
  }
}

class ChildPure extends React.PureComponent {
  render() {
    console.log("子组件render");
    return <h1>子组件随机数:{this.props.number}</h1>;
  }
}
export default PureApp;

index.js

import PureApp from "./5pure";
ReactDOM.createRoot(document.getElementById("root")).render(
  <PureApp></PureApp>
);

6.3.1 浅层对比

A. 说明:纯组件内部的对比是shallow compare(浅层对比)
B. 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
C.对于引用类型来说:只比较对象的引用(地址)是否相同
D.注意:state或props中属性值为引用类型时,应该创建新数据,不要直接修改数据!
shallow.js

import React from "react";

class ShallowRandom extends React.PureComponent {
  state = {
    obj: {
      number: 0,
    },
  };
  getRandom = () => {
    // 错误演示:直接修改原始对象中属性的值
    // const newObj = this.state.obj;
    // newObj.number = Math.floor(Math.random() * 2);
    // this.setState((state) => {
    //   return {
    //     obj: newObj,
    //   };
    // });

    //正确做法:创建新对象
    const newObj = { ...this.state.obj, number: Math.floor(Math.random() * 2) };
    this.setState(() => {
      return {
        obj: newObj,
      };
    });
  };
  render() {
    console.log("父组件render");
    return (
      <div>
        <h1>父组件随机数的值:{this.state.obj.number}</h1>
        <ChildRandom number={this.state.obj.number}></ChildRandom>
        <button onClick={this.getRandom}>重新生成随机数</button>
      </div>
    );
  }
}

class ChildRandom extends React.PureComponent {
  render() {
    console.log("子组件render");
    return <h1>子组件随机数:{this.props.number}</h1>;
  }
}

export default ShallowRandom;
import ShallowRandom from "./6shallow";
ReactDOM.createRoot(document.getElementById("root")).render(
  <ShallowRandom></ShallowRandom>
);

7.虚拟DOM和DIFF算法

A. React更新视图的思路是:只是state变化就重新渲染视图
B. 特点:思路非常清晰
C. 问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?不是
D. 理想状态:部分更新,只更新变化的地方
E. 问题:React是如何做到部分更新的?虚拟DOM配合Diff算法
F. 虚拟DOM:本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)
在这里插入图片描述

7.1 执行过程

A. 初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)。
B. 根据虚拟DOM生成真正的DOM,渲染到页面中
C. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)
在这里插入图片描述
A. 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容
B. 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面
在这里插入图片描述

7.2 代码演示

A. 组件render()调用后,根据状态和JSX结构生成虚拟DOM对象
B. 示例中,只更新P元素的文本节点内容
7.vdom.js

import React from "react";

class Vdom extends React.PureComponent {
  state = {
    number: 0,
  };
  getRandom = () => {
    this.setState(() => {
      return {
        number: Math.floor(Math.random() * 3),
      };
    });
  };
  render() {
    return (
      <div>
        <h1>随机数</h1>
        <h1>{this.state.number}</h1>
        <button onClick={this.getRandom}>重新生成</button>
      </div>
    );
  }
}

export default Vdom;

index.js

import Vdom from "./7vdom";
ReactDOM.createRoot(document.getElementById("root")).render(<Vdom></Vdom>);

8.总结

A. 工作角度:应用第一,原理第二
B. 原理有助于更好的理解React的自身运行机制
C. SetState()异步更新数据
D. 父组件更新导致子组件更新,纯组件提升性能
E. 思路清晰简单为前提,虚拟DOM和Diff保效率
F. 虚拟DOM->state+JSX
G. 虚拟DOM的真正价值从来都不是性能
H. 虚拟DOM使react脱离了浏览器的束缚

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1479257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高效备考2025年AMC8数学竞赛:2000-2024年AMC8真题练一练

如何提高小学和初中数学成绩&#xff1f;小学和初中可以参加的数学竞赛有哪些&#xff1f;不妨了解一下AMC8美国数学竞赛&#xff0c;现在许多小学生和初中生都在参加这个比赛。如果孩子有兴趣&#xff0c;有余力的话可以系统研究AMC8的历年真题&#xff0c;即使不参加AMC8竞赛…

live555学习 - 环境准备

环境&#xff1a;Ubuntu 16.04.7 ffmpeg-6.1 1 代码下载 最新版本&#xff1a; http://www.live555.com/liveMedia/public/ 历史版本下载 https://download.videolan.org/pub/contrib/live555/ 选择版本live.2023.01.19.tar.gz ps&#xff1a;没有选择新版本是新版本在…

SuMa++代码阅读记录

文章目录 流程梳理1. 打开点云文件2. 播放点云数据3. SUMA部分的流程图说明3.1 SUMA核心流程分析&#xff0c;其中也包含部分SUMA3.2 preprocess部分3.3 updatePose部分3.4 updateMap部分 4. SUMA中有关语义模型rangenet的部分4.1 下面是解析模型引擎4.2 下面这块是从配置文件中…

洛谷P1044题解

复制Markdown 展开 题目背景 栈是计算机中经典的数据结构&#xff0c;简单的说&#xff0c;栈就是限制在一端进行插入删除操作的线性表。 栈有两种最重要的操作&#xff0c;即 pop&#xff08;从栈顶弹出一个元素&#xff09;和 push&#xff08;将一个元素进栈&#xff09…

stm32触发硬件错误位置定位

1.背景 1. 项目中&#xff0c;调试过程或者测试中都会出现程序跑飞问题&#xff0c;这个时候问题特别难查找。 2. 触发硬件错误往往是因为内存错误。这种问题特别难查找&#xff0c;尤其是产品到了测试阶段&#xff0c;而这个异常复现又比较难的情况下&#xff0c;简直头疼。…

CDH6.3.1离线安装

一、从官方文档整体认识CDH 官方文档地址如下&#xff1a; CDH Overview | 6.3.x | Cloudera Documentation CDH是Apache Hadoop和相关项目中最完整、测试最全面、最受欢迎的发行版。CDH提供Hadoop的核心元素、可扩展存储和分布式计算&#xff0c;以及基于Web的用户界面和重…

虚拟游戏理财【华为OD机试-JAVAPythonC++JS】

题目描述 题目描述&#xff1a; 在一款虚拟游戏中生活&#xff0c;你必须进行投资以增强在虚拟游戏中的资产以免被淘汰出局。现有一家Bank&#xff0c;它提供有若干理财产品m&#xff0c;风险及投资回报不同&#xff0c;你有N&#xff08;元&#xff09;进行投资&#xff0c;能…

Python:练习:编写一个程序,录入一个美元数量(int),然后显示出增加%5税率后的相应金额。

案例&#xff1a; 编写一个程序&#xff0c;录入一个美元数量&#xff08;int&#xff09;&#xff0c;然后显示出增加%5税率后的相应金额。格式如下所示&#xff1a; Enter an amount:100 With tax added:$105.0 思考&#xff1a; 1、录入一个美元数量&#xff0c;录入&am…

qt学习:实战 记事本 + 快捷键 + 鼠标滚轮 + 打开读取写入关闭文件

目录 功能 步骤 配置ui界面 添加图片资源 添加头文件和定义成员数据和成员函数 在构造函数里初始化 增加当前字体大小函数 减小当前字体大小函数 在用户按下 Ctrl 键的同时滚动鼠标滚轮时&#xff0c;执行放大或缩小操作 多选框变化后发出信号绑定槽函数来改变编码 …

flutter学习(一) 安装以及配置环境

首先需要下载flutter&#xff0c;然后解压 然后配置环境变量&#xff0c;配置到bin目录就行 配置完之后cmd运行flutter doctor 你就会发现&#xff0c;都是错 此时脑海里响起&#xff0c;卧槽&#xff0c;怎么回事&#xff0c;咋办 别着急&#xff0c;我教你。。。 问题 这…

【QQ案例-QQ框架-主流框架 Objective-C语言】

一、接下来,我们来做一下这个QQ, 1.接下来,我们来做一下这个QQ, QQ框架啊, 这个东西呢,我们管它叫做“主流框架”, 首先呢,要告诉大家一点,这个东西呢,我们管它,叫做“主流框架”, 算是一个简称啊, 只能说,这种框架的类型,上边儿带navigation,下边儿带tabb…

Linux centos 变更MySQL数据存储路径

Linux centos 变更MySQL数据存储路径 登录mysql&#xff0c;查看数据存储路径创建新目录准备迁移数据检查是否配置成功 登录mysql&#xff0c;查看数据存储路径 mysql -u root -pshow global variables like "%datadir%";创建新目录 查看磁盘空间 df -h选取最大磁…

论文阅读:2015ResNet深度残差网络(待补充)

top5错误率&#xff1a;每张图片算法都会给出它认为最可能的五个类别&#xff0c;五个里面有一个是正确则算法预测正确。 技术爆炸1&#xff1a;2012年&#xff0c;DL和CNN用于CV&#xff1b;技术爆炸2&#xff1a;2015年&#xff0c;超过人类水平&#xff0c;网络可以更深&…

Unity-PDF分割器(iTextSharp)

PDF分割器 Unity-PDF分割器前言核心思路解决过程一、Unity安装iTextSharp二、运行时计算将要生成文件的大小三、分割核心代码四、使用StandaloneFileBrowser五、其他的一些脚本六、游戏界面主体的构建MainWindowWarningPanel & FinishPanel By-Round Moon Unity-PDF分割器 …

Vue3 在SCSS中使用v-bind

template 先创建一个通用的页面结构 <template><div class"v-bubble-bg"></div> </template>js 在JS中先对需要用的数据进行定义&#xff1a; 可以是参数&#xff0c;也可以是data <script setup>const props defineProps({bgCol…

Linux零基础快速入门

Linux的诞生 Linux创始人:林纳斯 托瓦兹 Linux 诞生于1991年&#xff0c;作者上大学期间 因为创始人在上大学期间经常需要浏览新闻和处理邮件&#xff0c;发现现有的操作系统不好用,于是他决心自己写一个保护模式下的操作系统&#xff0c;这就是Linux的原型&#xff0c;当时他…

【笔记】:更方便的将一个List中的数据传入另一个List中,避免多重循环

这里是 simpleInfoList 集合&#xff0c;记为集合A&#xff08;传值对象&#xff09; List<CourseSimpleInfoDTO> simpleInfoList courseClient.getSimpleInfoList(courseIds);if(simpleInfoListnull){throw new BizIllegalException("当前课程不存在!");}这…

[Flutter]TextButton自定义样式

在Flutter中&#xff0c;TextButton是一个简单的文本按钮&#xff0c;它遵循当前的Theme。如果你想要修改TextButton的样式&#xff0c;可以通过设置其style属性来实现&#xff0c;该属性接收一个ButtonStyle对象。 给TextButton和里面Text添加背景后&#xff0c;效果如下。可…

设计模式(二)单例模式

单例模式&#xff1a;确保一个类只有一个实例&#xff0c;并提供了全局访问点&#xff1b;主要是用于控制共享资源的访问&#xff1b; 单例模式的实现分为懒汉式和饿汉式。 懒汉式单例在需要时才会创建&#xff0c;而饿汉式单例则在类加载时立即创建实例&#xff1b; 单例模…

【精准获客,优化体验】Xinstall助力企业提升App渠道效果与用户满意度

在数字化时代&#xff0c;企业对于用户体验的重视程度日益提升。为了更好地满足用户需求&#xff0c;提供个性化、专属化的服务已经成为企业竞争的关键。今天&#xff0c;我们将向大家介绍一款名为Xinstall的一站式App全渠道统计服务商&#xff0c;它凭借强大的技术实力和丰富的…