当前位置: 首页 > news >正文

react 基础知识(一)

1、 安装1 (版本 react 18)

// 安装全局脚手架(create-react-app基于webpack+es6)
npm install -g create-react-app
//使用脚手架搭建项目
create-react-app my-app
// 打开目录
cd my-app
// 运行项目
npm start

目录结构

2、初体验

import React from 'react';
// 17写法
import ReactDOM from 'react-dom';
const root = document.getElementById('root');
ReactDOM.render(<App />, root);
// 18写法
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>hello</h1>
);

3、JSX(javascript+xml的语法)

概念:jsx是js和html混合的语法,将组件结构、数据、样式聚合在一起定义组件,会编译成普通的js

  • 遇到 < 开头,用html规则解析(标签必须闭合
  • 遇到 { 开头,用js规则解析
  • 样式style等于一个对象(属性使用驼峰式命名)
  • 声明类名用clssName=类名,因为class是js关键字
export default function App() {return (<div className="App">// { }里面是js表达式(变量的运算符组合,如a+b),必须返回一个值{arr.map(function(item,index){// 渲染后为style="background-color:green"return <span style={{backgroundColor:'green'}} className='read_name' key={index}>{item}</span>})}</div>);
}
  • 判断使用三目运算符、if、&&
    注: 勿将数字放在 && 左侧 {num&& < A />} 当num=0时,会渲染0
// 三目运算	{flag? <A /> : <B />} 表示 “当 flag为真值时, 渲染 <A />,否则 <B />”。
return <span style={{backgroundColor:index==0?'green':"pink"}} >张三</span>
// if判断	选择性返回jsx
if(flag){return <h1>张三 √</h1>}
return <h1>张三 </h1>
// &&	{flag&& <A />} 表示 “当 flag为真值时, 渲染 <A />,否则不渲染”
return (<h1>张三 {flag&& '✔'}</h1>
);
  • 渲染列表用map,filter可以筛选需要的组件
    循环列表时必须添加key值
// map遍历见上示例
// filter 示例
arr=[{name:'张三',age:20},{name:'李四',age:10}]
let newArr=arr.filter(item=>item.age>18)

jsx中使用js的规则:

  • 引号包含的值作为字符串传递
  • {} 包含js 逻辑与变量
  • {{}} 表示包含的是一个js对象
  • 它们在内容区域属性= 后面生效

4、组件

页面分割成多个组件,每个组件有自己的逻辑与样式,不同组件可组合成一个新的页面。可组合、可重用、可维护
组件类第一个字母必须大写,只能包含一个顶层标签可用div或者是空标签<></>包起来
注:凡是首字母小写都会被识别为react元素(html标签)

(1)声明组件(导出-声明-添加)

// 第一步:导出组件 export default  一个文件只能有一个default
// 第二步:声明组件   function App(){}
export default function App() {
// 第三步:添加标签
// return 与标签不在一行,必须用()b包起来,否则什么都不会返回return (<div className="App">hello</div>);// 或写成一行// return  <div className="App">hello</div>
}

(2)导入与导出

同一文件中,有且仅有一个默认导出,但可以有多个具名导出
图解

(3)创建组件的2种方式

第一种:函数式声明 ,function App(){} 静态组件,见(1)示例
第二种:类名组件 class App extends Component{}

import React,{Component } from 'react';
export default class App extends Component {// render 指该组件如何渲染,一定要返回一个react元素,且只能有一个根元素render(){return <h1>hello</h1>}
}

5、props(父子组件传参)

  • 传递props,需要将参数加到jsx中
  • 读取props,可使用function App({name,id})的解构语法
  • 可以指定默认值,函数式声明直接用name=“李四”,class声明用static defaultProps={name:‘李四’}
//1、函数式的demo
// 这里使用结构取出name,也可写成props.name
function Child({name}) { 
//可以给name附初始值 name="李四",当调用时没有传值,会使用默认值
// function Child({name="李四"}) { return (<span>{name}</span>);
}
export default function App() {return (<div className="App"><Child name="张三"></Child></div>);  
}
// 2、class类的demo
class Children extends Component{static defaultProps={name:'李四'} // 默认属性值,即父类调用不传递props,会使用这个默认的值render(){return(<h1>{this.props.name}</h1>)}
}
export default class App extends Component {  render(){return (<div><Children name="张三"></Children><Children ></Children> {/* 未传值会显示默认值“李四” */}</div>)}
}
  • class声明的组件可以使用prop-types指定props的类型和是否必填
// 第一步安装插件
npm install props-types -S 安装依赖
// 引入PropTypes 
import PropTypes from 'prop-types' 
class Children extends Component{// 定义组件的类型和是否必传static propTypes={name:PropTypes.string, // name类型为stringage:PropTypes.number.isRequired // age   number,且必填}render(){return(<div><h1>{this.props.name}</h1><div>年龄:{this.props.age}</div></div>)}
}
export default class App extends Component {render(){return (<div>{/* 错误写法 */}<Children name={1}></Children>{/*会提示报错,The prop `age` is marked as required in `Children`, but its value is `undefined`.Invalid  prop `name` of type `number` supplied to `Children`, expected `string`*/}{/* 正确写法 */}<Children name="张三" age={1}></Children></div>)}
}

prop-types 常用类型 :array、bool、func、object、number、string、symbol、element
限制必要:PropTypes.类型.isRequired
仅限制必要:PropTypes. any.isRequired
多种类型:PropTypes.oneOfType([[PropTypes.string,PropTypes.number ])
多可选类型:PropTypes.oneOf([‘张三’,‘李四’])
特定结构对象:PropTypes.shape({name:类型,age:类型})

// 多种类型--以下代码就不会报错static propTypes={name:PropTypes.oneOfType([PropTypes.string,PropTypes.number]),}
<Children name={1}></Children>
// 多选类型--只能传递red和blue中的一个值,否则会报错,不能不传
static propTypes={color:PropTypes.oneOf(['red','blue']),}<Children color="red"></Children>  // 特定结构对象, 符合结构的才可以static propTypes={obj:PropTypes.shape({name: PropTypes.string.isRequired,age: PropTypes.number.isRequired})} // 符合obj={name:'xxx',age:数字}<Children obj={{name:'张三',age:14}}></Children>
  • props是只读,不可被修改
  • 像< Card>< Avatar />< /Card>这样的嵌套 JSX,将被视为 Card 组件的 children prop (类似于插槽)
function Card({ children }) {return (<div className="card">{children}</div>);
}export default function App() {return (<Card><Avatarsize={100}person={{ name: 'Katsuko Saruhashi',imageId: 'YfeOqp2'}}/></Card>);
}

6、状态state和事件处理

(1)state状态:组件内部变化的值

import React,{Component } from 'react';
export default class App extends Component {constructor(){super();this.state={name:'张三'}  // 自定义组件状态对象(组件内部变化的值,内部初始化内部改变)}// 生命周期函数   组件挂载完成componentDidMount(){// 调用setState ,状态会更新,还会重新调用render方法重新渲染this.setState({name:'李四'})}render(){return <h1>{this.state.name}</h1>}
}

setState是异步,不能在赋值后立即获取新state,可在其回调函数中获取

// 会先打印修改前的值,再打印修改后的值
handleClick=()=>{this.setState({num:this.state.num+1},()=>{console.log("获取修改后的值",this.state.num)})console.log("获取修改前的值",this.state.num)}

(2)事件处理:

class声明-----onClick={this.方法名}
function声明-----onClick={方法名}

// class声明onClick={this.方法名} 
export default class App extends Component {constructor(){super();this.state={heart:true}  }handleClick=()=>{this.setState({heart:!this.state.heart})}render(){return (<div><span>心情{this.state.heart?'开心':'伤心'}</span><button onClick={this.handleClick}>变心</button></div>)}
}
// 函数式声明:onClick={方法名}--无实例无this
export default function App(){const handleClick=()=>{console.log('dianji')}return(<><button onClick={handleClick}>点击</button></>)
}

7、表单组件的双向绑定、refs

(1)双向绑定

受控组件:受当前组件的状态控制
非受控组件:不受当前组件状态控制

export default class App extends Component{constructor(){super()this.state={name:'张三'}}handleChange=(event)=>{// 通过获取输入框的值实现双向绑定this.setState({name:event.target.value})}render(){return (<div><div>hello,{this.state.name}</div>{/* 受控组件,必须加change事件 */}<input onChange={this.handleChange} value={this.state.name}></input>{/* 非受控组件,随便输 */}<input onChange={this.handleChange} ></input></div>)}
}

(2)refs:可获取DOM元素及其实例

class有三种(refs字符串、 refs回调函数、createRef)、

// class声明有实例,所以可以用字符串refs|回调函数|createRef()三种方式,以下是示例
export default class App extends Component{constructor(){super()this.myRef=React.createRef() // 3、createRef方式}handleChange=(event)=>{console.log(this.refs.test.value,'方式一:已废弃')console.log(this.b.value,'方式二:回调函数')console.log(this.myRef.current.value,'方式三:createRef,获取元素是.current')}render(){return (<div  onChange={this.handleChange}>{/* refs--字符串方式,已经被废弃,使用会被浏览器警告 */}<input ref="test"></input>{/* refs--回调函数,会执行2次,一次传参null,一次传参dom元素 */}<input ref={ref=>this.b=ref}></input>{/* refs--createRef方式,dom元素为.current属性*/}<input  ref={this.myRef}></input></div>)}
}

function也有三种(useRef、React.forwardRef、React.useImperativeHandle)

// 因为function没有实例,所以不能用class的三种refs的方式
// 方式一:useRef,.current获取组件实例
export default function App(){const myRef=useRef(null)const handleClick=()=>{myRef.current.focus()}return(<><input type="text" ref={myRef} /><button onClick={handleClick}>点击聚焦</button></>)
}
// 方式二:forwardRef,该ref绑定的值是组件内ref绑定的节点而非组件实例,组件实例是不可以访问该回调ref函数
import React, { Component, forwardRef} from 'react';
const Test=forwardRef(function (props,ref){return <h1 ref={ref}></h1>
})
export default class App extends Component{constructor(){super()this.myRef=null}handleClick=()=>{this.myRef.innerHTML="加油哦"}render(){return (<div><Test type="text" ref={ref=>this.myRef=ref} /><button onClick={this.handleClick}>点击聚焦</button></div>)}
}
//方式三:useImperativeHandle,结合forwardRef使用
const Test=forwardRef(function (props,ref){const inputRef = React.useRef();React.useImperativeHandle(ref, () => ({focus: () => {inputRef.current.focus();},}));return <input ref={inputRef }/>
})
export default class App extends Component{constructor(){super()this.myRef=null}handleClick=()=>{this.myRef.focus()}render(){return (<div><Test type="text" ref={ref=>this.myRef=ref} /><button onClick={this.handleClick}>点击聚焦</button></div>)}
}

8、组件的生命周期

(1)class声明才有生命周期

声明周期图
声明周期执行示例:

export default class App extends Component{constructor(){super()this.state={num:0}console.log('constructor:初始化')}static getDerivedStateFromProps(){console.log('getDerivedStateFromProps:组件即将加载或发现state、props变化')return null}handleClick=()=>{console.log('准备变值')this.setState({num:this.state.num+1},()=>{console.log("获取修改后的值",this.state.num)})}shouldComponentUpdate(newProps,newState){console.log('shouldComponentUpdate:是否应该变化')return !newState.num%5==0}getSnapshotBeforeUpdate(){console.log('getSnapshotBeforeUpdate:将要更新')return null}componentDidUpdate(){console.log('componentDidUpdate:更新结束')}render(){console.log('render:组件开始挂载')return (<div><p>{this.state.num}</p><button onClick={this.handleClick}>点击聚焦</button></div>)}componentDidMount(){console.log('componentDidMount:组件挂载完成')}componentWillUnmount(){console.log('componentWillUnmount:卸载前')}}

挂载更新
注:componentWillMount、componentWillUpdate、componentWillReceiveProps已经被官方废弃,请尽量用上图的生命周期函数

(2)函数式声明没有生命周期,一般用useEffect替代

其作用同componentDidMount、componentDidUpdate、componentWillUnmount

export default function App(){const [num, setNum] = useState(0);const [num1, setNum1] = useState(1)// 1、useEffect 第二个参数为空useEffect(()=>{}),初始化渲染之后执行一次,当页面所有数据变化时也会执行一遍,同componentDidMount、componentDidUpdate,-----初始化执行、点击按钮1和2都会执行// 2、useEffect 第二个参数为空数组useEffect(()=>{},[]),初始化渲染render之后只执行一次,数据变化不执行,同componentDidMount---初始化执行、点击按钮1、2不执行// 3、useEffect 第二个参数为监听值,useEffect(()=>{},[num]),初始化渲染render之后执行一次,监听元素变化执行一次,同componentDidMount、componentDidUpdate----初始化执行、点击按钮2才会执行useEffect(()=>{console.log('useEffect')},[num1])// 4、useEffect 第一个函数返回一个函数,表示在组件卸载前执行,同componentWillUnmount  useEffect(()=>{return ()=> {// 在此走一些收尾工作,如清除定时器/取消订阅等}},[stateValue])return(<><p>{num}</p><button onClick={()=>setNum(num+1)}>点击1</button><p>{num1}</p><button onClick={()=>setNum1(num1+1)}>点击2</button></>)
}

  1. 若安装脚手架时报以下错,将npm切换成淘宝镜像即可安装成功
    报错截图 ↩︎

相关文章:

react 基础知识(一)

1、 安装1 &#xff08;版本 react 18&#xff09; // 安装全局脚手架&#xff08;create-react-app基于webpackes6&#xff09; npm install -g create-react-app //使用脚手架搭建项目 create-react-app my-app // 打开目录 cd my-app // 运行项目 npm start2、初体验 impo…...

SpringBoot整合JUnit、MyBatis、SSM

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 SpringBoot整合 一、SpringBoot整合JUnit二、Spri…...

virtuoso61x中集成calibre

以virtuoso618为例&#xff0c;在搭建完电路、完成前仿工作之后绘制版图&#xff0c;版图绘制完成之后需要进行drc和lvs【仅对于学校内部通常的模拟后端流程而言】&#xff0c;一般采用mentor的calibre来完成drc和lvs。 服务器上安装有virtuoso和calibre&#xff0c;但是打开la…...

com.google.guava:guava 组件安全漏洞及健康分析

组件简介 维护者google组织许可证类型Apache-2.0首次发布2010 年 4 月 26 日最新发布时间2023 年 8 月 1 日GitHub Star48189GitHub Fork10716依赖包28,694依赖存储库219,576 Guava 是 Google 的一组核心 Java 库&#xff0c;其中包括新的集合类型&#xff08;例如 multimap 和…...

Hadoop服务脚本

#!/bin/bash process("NameNode" "SecondaryNameNode" "DataNode" "NodeManager" "ResourceManager") JAVA_HOME"/opt/software/jdk1.8.0_371" HADOOP_HOME"/opt/software/hadoop-3.3.6"# 定义颜色的AN…...

[QT]设置程序仅打开一个,再打开就唤醒已打开程序的窗口

需求&#xff1a;speedcrunch 这个软件是开源的计算器软件。配合launch类软件使用时&#xff0c;忘记关闭就经常很多窗口&#xff0c;强迫症&#xff0c;从网上搜索对版本进行了修改。 #include "gui/mainwindow.h"#include <QCoreApplication> #include <…...

数据库(二) Oracle篇

Oracle SQL常用函数 概述 SQL函数有单行函数和多行函数,其区别为&#xff1a; 单行&#xff1a;输入一行,返回一行,如字符、数字、转换、通用函数等多行&#xff1a;输入多行,返回一行,也称为分组函数、组函数、聚合函数,且多行函数会自动滤空 单行函数 字符函数 CONCAT(…...

TDengine函数大全-目录

TDengine函数大全 详情见具体页面&#xff0c;点击进入。 1.数学函数 ABSACOSASINATANCEILCOSFLOORLOGPOWROUNDSINSQRTTAN 2.字符串函数 CHAR_LENGTHCONCATCONCAT_WSLENGTHLOWERLTRIMRTRIMSUBSTRUPPER 3.转换函数 CAST TO_ISO8601TO_UNIXTIMESTAMPTO_JSON 4.时间和日期…...

代理模式之静态代理

代理模式是一种常见的设计模式&#xff0c;它允许一个对象&#xff08;代理对象&#xff09;代表另一个对象&#xff08;真实对象&#xff09;进行操作。在软件开发中&#xff0c;代理模式被广泛应用于各种场景&#xff0c;例如网络请求的代理、权限控制的代理等。 静态代理是…...

LeetCode——栈的压入、弹出序列

这里我用下面的例子子来讲解一下模拟栈的实现。 例子1&#xff1a;pushed [1,2,3,4,5] popped [4,5,3,2,1] 思路&#xff1a;第一步&#xff1a;我们先创建一个栈&#xff0c;然后将pushed的数据压进去 第二步&#xff1a;判断&#xff01; 当压入栈的数据和popped第一个数据…...

Flutter 逆向安全

前言&#xff1a; 前几天在 "学习" 一个项目&#xff0c; 发现是用 Flutter 开发的。之前研究过 flutter 的逆向&#xff0c;早期 Flutter 有工具可以通过快照进行反编译&#xff1a;《对照表如下》 新的版本开发者没有维护了。 目前没有很好的工具 可以对 Flutter 进…...

【微服务部署】01-Kubernetes部署流程

文章目录 部署1. Kubernetes是什么2. Kubernetes的优势3. 环境搭建4. 应用部署 部署 1. Kubernetes是什么 Kubernetes是一个用于自动部署、扩展和管理容器化应用程序的开源系统 2. Kubernetes的优势 自动化容器部署资源管理与容器调度服务注册发现与负载均衡内置配置与秘钥…...

SPI3+DMA外设驱动-TFTLCD初始化

前言 &#xff08;1&#xff09;本系列是基于STM32的项目笔记&#xff0c;内容涵盖了STM32各种外设的使用&#xff0c;由浅入深。 &#xff08;2&#xff09;小编使用的单片机是STM32F105RCT6&#xff0c;项目笔记基于小编的实际项目&#xff0c;但是博客中的内容适用于各种单片…...

通过chatgpt 学习React的useEffect

定义&#xff1a; useEffect 是 React 中的一个 Hook&#xff0c;它用于处理函数组件中的副作用操作。副作用操作可以包括数据获取、订阅事件、定时器等。 useEffect 接受两个参数&#xff1a;第一个参数是一个回调函数&#xff0c;用于执行副作用操作&#xff1b;第二个参数…...

rabbitMq介绍及使用

点击跳转https://blog.csdn.net/qq_43410878/article/details/123656765...

rabbitmq载在.net中批量消费的问题记录

背景 最近遇到了一个问题&#xff0c;在使用rabbitmq的时候出现了丢消息、消息重复消费等一系列的问题&#xff0c;使用的是.net框架&#xff0c;背景是高并发压力下的mq消费&#xff0c;按理说即使队列中堆了几百条消息&#xff0c;我客户端可以同处理5个消息。 原因是多线程…...

【RPC 协议】序列化与反序列化 | lua-cjson | lua-protobuf

文章目录 RPC 协议gRPCJSON-RPC 数据序列化与反序列化lua-cjsonlua-protobuf RPC 协议 在分布式计算&#xff0c;远程过程调用&#xff08;英语&#xff1a;Remote Procedure Call&#xff0c;缩写为 RPC&#xff09;是一个计算机通信协议。该协议允许运行于一台计算机的程序调…...

Flutter的Timer类

文章目录 一、Timer简介Timer类的详细介绍导入dart:async包创建一个定时器取消定时器定时器的周期性执行注意事项 二、Semantics简介Flutter Semantics 的详细介绍SemanticsNode语义标签和标签形状语义属性自定义语义行为通过语义树导航 一、Timer简介 Flutter的Timer类是Dart…...

Chrome小恐龙快跑小游戏——Python实现

目录 视频演示 代码实现 视频演示 Chrome小恐龙快跑小游戏——Python实现 代码实现 import pygame import os import random pygame.init()# Global Constants SCREEN_HEIGHT 600 SCREEN_WIDTH 1100 game_over False SCREEN pygame.display.set_mode((SCREEN_WIDTH, SCR…...

Web网站服务器

目录 一、什么是Apache? 二、虚拟目录是什么&#xff1f; 三、Apcahe相关配置文件 四、httpd.conf主配置文件的常用配置参数 五、Web网站配置案例 5.1搭建基于用户的个人主页网站 5.2、配置虚拟目录 5.3、配置虚拟主机 5.3.1搭建两个基于IP地址的虚拟主机 5.3.2搭建两个基于域…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...