[React]利用Webcomponent封装React组件
[React]利用Webcomponent封装React组件
为什么这么做
我个人认为,最重要的点是可以很方便地跨框架挂载和卸载wc元素(至少我在项目里是这么玩的),此外,基于wc的css沙箱以及它的shadowRoot机制,可以提供一套隔离机制,保证每个渲染组件的边界分明。
利用AI总结罗列了一下都有啥优点…
- 封装性:Web Components提供了一种封装UI组件的方法,使得组件可以在不同的框架或无框架环境中重用。
- 可重用性:封装为Web Components的React组件可以在任何支持Web Components的环境中使用,不限于React应用。
- 封装的样式和行为:Web Components允许你封装组件的HTML结构、样式和行为,确保样式和行为不会泄露到父组件或全局作用域。
- 独立性:Web Components封装的组件具有独立性,它们拥有自己的DOM树和作用域,不会影响外部环境。
- 易于集成:Web Components提供了一种标准化的集成方式,可以更容易地将React组件集成到其他Web应用中。
- 更好的性能:Web Components的自定义元素可以在不影响主线程的情况下进行升级和渲染,这有助于提高应用性能。
- 标准化:Web Components基于W3C标准,这意味着它们在不同的浏览器和环境中具有更好的一致性和兼容性。
- 易于维护:由于Web Components封装的组件具有清晰的接口和封装性,维护和更新组件变得更加容易。
- 样式隔离:Web Components的Shadow DOM技术可以确保组件的样式不会受到外部样式的影响,同时也防止组件内部样式泄露到外部。
- 生命周期管理:Web Components允许你定义组件的生命周期钩子,如
connectedCallback
、disconnectedCallback
等,这与React组件的生命周期方法类似。 - 跨框架使用:封装为Web Components的React组件可以被其他前端框架或库使用,例如Vue、Angular或原生JavaScript。
- 自定义元素:Web Components允许开发者定义自定义HTML元素,这些元素可以像标准HTML元素一样使用。
- 易于测试:Web Components的封装性使得测试组件变得更加简单,因为你可以独立于其他组件来测试它们。
- 更好的封装和抽象:Web Components提供了一种封装和抽象UI组件的方式,使得组件的实现细节对使用者是透明的。
Webcomponent入门
先来简单地过一下webcomponent的基础
官方文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components
示例
下面是一个最简单的示例,自定义了一种名为”simple-component“的元素,并且它没有shadowRoot(意味着它并没有与外界隔离样式)。
class SimpleComponent extends HTMLElement {constructor() {super();this.innerHTML = `<p>Hello, World!</p>`;}
}customElements.define('simple-component', SimpleComponent);
下面是一个内容更丰富一些的示例,有基础的大概过一眼也知道大概了。
// 1.自定义标签都是用class 的形式去继承
class myDiv extends HTMLElement {// 监听static get observedAttributes() {return ['option']}constructor() {super()// 这样我们才能够去追加元素this.attachShadow({ mode: 'open' })}// 重要:生命周期方法 开始connectedCallback() {console.log('connectedCallback生命周期')this.render({option: this.getAttribute('option'),})// 获取元素console.log(this.shadowRoot.querySelector('.content'))console.log('this.shadowRoot: ', this.shadowRoot)document.addEventListener('click', e => {// 重要:冒泡的顺序,通过这个可以判断有没有在鼠标内部进行点击if (e.composedPath().includes(this)) {console.log('点击了里面')}})this.shadowRoot.querySelector('.content').addEventListener('click', e => {console.log('e: ', e)// window.dispatchEvent})}// 重要:生命周期方法 重新渲染 .甚至还是第一次进行渲染,比connect还快// 会重新渲染 connectCallbackattributeChangedCallback(attr, oldValue, newValue) {if (oldValue) {switch (attr) {case 'option':this.shadowRoot.querySelector('.title').textContent = newValue}}console.log('attributeChangeCallback', attr, oldValue, newValue)}borderAdd() {console.log('borderadd')this.shadowRoot.querySelector('.content').style.border = '3px solid green'}render(data) {let { option } = data// console.log()let nodeTemplate = document.createElement('template')nodeTemplate.innerHTML = `<div class="content" ><div class="title">${option} </div> <slot name="container"></slot></div>`let nodeStyles = document.createElement('style')// shadow dom 的样式绝对隔离// 重要: :host选择器可以选中根也就是my-div的样式。外面的选择器样式要高于这个nodeStyles.innerHTML = `:host(.active) .content{margin-top:20px;background:rgba(0,0,0,30%);}:host{display:block}.content{width:100px;height:100px;background:rgba(0,0,0,20%)}::slotted([slot="container"]){display:none}::slotted(.active){display:block}`this.shadowRoot.appendChild(nodeTemplate.content)this.shadowRoot.appendChild(nodeStyles)setTimeout(() => {this.borderAdd()}, 3000)}
}// 名字必须小写 驼峰必须要转成横线
customElements.define('my-div', myDiv)
shadowRoot
一个Web组件可以有且仅有一个shadowRoot
。shadowRoot
是与该组件关联的影子DOM的根节点。当使用attachShadow
方法创建影子DOM时,它会返回一个shadowRoot
对象,这个对象是唯一的,并且与创建它的元素关联。
例如:
class MyComponent extends HTMLElement {constructor() {super();this.shadow = this.attachShadow({ mode: "open" });this.shadow.innerHTML = `<p>I am in the shadow DOM!</p>`;}
}customElements.define('my-component', MyComponent);
在这个例子中:
MyComponent
类扩展了HTMLElement
,定义了一个Web组件。- 在构造函数中,通过调用
this.attachShadow({ mode: "open" })
创建了一个shadowRoot
,并将其存储在this.shadow
变量中。 - 这个
shadowRoot
是唯一的,并且与MyComponent
实例关联。
关键点:
- 唯一性:每个Web组件实例只能有一个
shadowRoot
。 - 关联性:
shadowRoot
与创建它的Web组件实例是紧密关联的,不能被其他组件实例访问。
因此,尽管可以在shadowRoot
内创建多个子元素和结构,但每个Web组件实例只能有一个shadowRoot
。这有助于保持组件的封装性和独立性。
生命周期
connectedCallback
:- 当自定义元素被插入到文档DOM树中时调用此方法。这类似于React中的
componentDidMount
。
- 当自定义元素被插入到文档DOM树中时调用此方法。这类似于React中的
disconnectedCallback
:- 当自定义元素从DOM树中移除时调用此方法。类似于React中的
componentWillUnmount
。
- 当自定义元素从DOM树中移除时调用此方法。类似于React中的
attributeChangedCallback
:- 当自定义元素的属性被更改时调用此方法。它接收三个参数:属性名称、旧值和新值。这可以用于响应属性的变化,类似于React中的
componentDidUpdate
,但是它是针对属性而不是状态。
- 当自定义元素的属性被更改时调用此方法。它接收三个参数:属性名称、旧值和新值。这可以用于响应属性的变化,类似于React中的
adoptedCallback
:- 当自定义元素被移动到新的文档时调用此方法。这在Web Components中是特有的,因为自定义元素可以跨文档使用。
主要属性
- 观察者模式(Observed attributes):
- 通过在自定义元素类中定义一个静态的
observedAttributes
属性数组,可以指定哪些属性的更改应该触发attributeChangedCallback
。
- 通过在自定义元素类中定义一个静态的
connected
和disconnected
属性:- 这些属性可以用于检查自定义元素是否已经连接到文档的DOM树中。
shadowRoot
属性:- 每个自定义元素都有一个
shadowRoot
属性,它是一个Shadow DOM树的根。可以在这个属性上使用生命周期回调来管理Shadow DOM的创建和更新。
- 每个自定义元素都有一个
constructor
:- 虽然不是Web Components的生命周期回调,但是自定义元素的构造函数是定义元素属性和方法的地方,并且在元素实例化时调用。
Lit框架入门
一般知道了上面的基础,就可以写wc组件了,但实际开发中,肯定还是需要借助一些已有的开发框架来辅助开发,而Lit就是目前最成熟且使用量最高的。
原理介绍
Web组件的更新并不是每次都进行全量更新。Web组件的更新机制非常灵活,能够根据组件的状态和属性的变化来决定是否需要更新。以下是一些关键点:
-
属性变化触发更新:
- Web组件的更新通常是由属性的变化触发的。当组件的属性发生变化时,浏览器会调用
attributeChangedCallback
方法来处理这些变化。
- Web组件的更新通常是由属性的变化触发的。当组件的属性发生变化时,浏览器会调用
-
状态变化触发更新:
- 组件的内部状态变化也可能导致更新。例如,在LitElement中,当使用
@property
装饰器定义的属性发生变化时,会触发更新。
- 组件的内部状态变化也可能导致更新。例如,在LitElement中,当使用
-
生命周期方法:
- 组件的生命周期方法,如
connectedCallback
,disconnectedCallback
,adoptedCallback
,firstUpdated
,updated
等,都可以在特定时机触发更新。
- 组件的生命周期方法,如
-
选择性更新:
- 更新机制可以是选择性的。例如,在LitElement中,可以通过使用
requestUpdate
方法来请求更新,而不必每次都进行全量更新。
- 更新机制可以是选择性的。例如,在LitElement中,可以通过使用
-
虚拟DOM:
- 一些Web组件框架(如LitElement)使用虚拟DOM技术来优化更新过程。虚拟DOM可以比较组件的新旧状态,并只更新那些实际发生变化的部分。
-
优化性能:
- 为了避免不必要的全量更新,Web组件通常会使用一些优化技术,例如节流(throttle)和防抖(debounce)来减少更新次数。
-
自定义渲染逻辑:
- 开发者可以通过自定义渲染逻辑来控制组件的更新过程。例如,可以在
render
方法中手动决定哪些部分需要重新渲染。
- 开发者可以通过自定义渲染逻辑来控制组件的更新过程。例如,可以在
-
条件渲染:
- 组件可以通过条件渲染来决定是否需要更新某些部分。例如,只有当特定条件满足时才重新渲染某些元素。
示例
以下是一个使用LitElement的示例,展示了如何控制组件的更新:
import { LitElement, html, css, property } from 'lit';class MyComponent extends LitElement {@property({ type: String })message = '';render() {return html`<div><p>${this.message}</p></div>`;}updated(changedProperties) {super.updated(changedProperties);if (changedProperties.has('message')) {console.log('Message updated:', this.message);}}
}customElements.define('my-component', MyComponent);
在这个示例中:
message
属性使用@property
装饰器定义,当其值发生变化时,会触发组件的更新。render
方法定义了组件的渲染逻辑,只有当message
属性发生变化时,相关的部分才会重新渲染。updated
方法在组件更新后被调用,可以在这里处理更新后的逻辑。
通过这种方式,Web组件可以有效地控制更新过程,避免不必要的全量更新,从而提高性能。
增加的生命周期和内置属性
Lit 相对于传统 Web 组件规范增加的一些生命周期钩子和特性:
-
render
方法:- 这是 Lit 的核心特性之一。
render
方法是一个返回组件模板的函数,Lit 会根据这个方法的内容来渲染组件的 UI。
- 这是 Lit 的核心特性之一。
-
update
方法:- 这个方法在组件的属性或状态发生变化时被调用。Lit 会调用这个方法来决定是否需要重新渲染组件。
-
shouldUpdate
方法:- 这个方法允许开发者自定义更新逻辑,决定是否需要进行更新。如果返回
false
,则跳过更新。
- 这个方法允许开发者自定义更新逻辑,决定是否需要进行更新。如果返回
-
willUpdate
方法:- 在组件更新之前被调用,可以用于执行更新前的准备工作。
-
updated
方法:- 在组件更新之后被调用,可以用于执行更新后的逻辑处理。
-
firstUpdated
方法:- 在组件首次更新后被调用。这与 Web 组件的
connectedCallback
有些相似,但专门用于处理首次渲染后的逻辑。
- 在组件首次更新后被调用。这与 Web 组件的
-
connectedCallback
:- 这是 Web 组件规范中的方法,Lit 也支持。当组件被插入到文档中时调用。
-
disconnectedCallback
:- 这是 Web 组件规范中的方法,Lit 也支持。当组件从文档中移除时调用。
-
attributeChangedCallback
:- 这是 Web 组件规范中的方法,Lit 也支持。当组件的属性发生变化时调用。
-
adoptedCallback
:- 这是 Web 组件规范中的方法,Lit 也支持。当组件被移动到新文档时调用。
-
requestUpdate
方法:- 这个方法可以被开发者调用,以请求更新组件的属性。Lit 会安排在下一个微任务中处理这些更新。
-
updateComplete
Promise:- 一个 Promise,当组件的更新完成后会解析。这可以用于在更新完成后执行异步操作。
-
样式管理:
- Lit 提供了
CSSResult
和unsafeCSS
等 API,用于更安全和方便地管理组件的样式。
- Lit 提供了
-
属性装饰器:
- 使用
@property
装饰器定义的属性会触发更新,并且可以指定属性的类型和是否同步到 DOM 属性。
- 使用
-
状态管理:
- Lit 通过
state
方法和reactive
装饰器,提供了一种声明式的方式来管理组件的状态。
- Lit 通过
核心:结合Lit框架实现React组件封装
那么基于以上,我们可以很容易地就实现利用Lit框架创造出一个webcomponent容器,然后用来包裹React组件。
Base基础类
import { LitElement, ReactiveElement, adoptStyles, unsafeCSS, PropertyValues } from 'lit'
import { property } from 'lit/decorators.js'type ThrottleFn = (...args: any[]) => void
type DelayFn = (fn: ThrottleFn) => voidconst throttleWith = <T extends ThrottleFn>(fn: T,delayFn: DelayFn,leading = false
): T => {let lastArgs: Parameters<T>, lastThis: unknown, isWaiting = falseconst throttledFn = (...args: Parameters<T>) => {lastArgs = args// eslint-disable-next-linelastThis = thisif (!isWaiting) {if (leading) {fn.apply(lastThis, lastArgs)}isWaiting = truedelayFn(() => {fn.apply(lastThis, lastArgs)isWaiting = false})}}return throttledFn as T
}export default class Base extends LitElement {private _wcStyle?: string@property({ attribute: 'wc-style' })get wcStyle() {return this._wcStyle}set wcStyle(val: string | undefined) {this._wcStyle = valthis.adoptStyles()}/*** 使事件不能跨越ShadowDOM边界传播*/@property({ type: Boolean, attribute: 'prevent-compose' })protected preventCompose = false/*** 使事件不冒泡*/@property({ type: Boolean, attribute: 'prevent-bubbles' })protected preventBubbles = false// 应用样式protected adoptStyles = throttleWith(() => {const apply = () => {if (this.renderRoot instanceof ShadowRoot) {const styles = (this.constructor as typeof ReactiveElement).elementStyles.slice() // 获取原有样式this.wcStyle && styles.push(unsafeCSS(this.wcStyle))adoptStyles(this.renderRoot, styles) // 重新应用样式}}this.renderRoot ? apply() : this.updateComplete.then(apply)},(fn: any) => Promise.resolve().then(fn))// 派发事件emit(eventName: string, detail?: any, options?: CustomEventInit) {let event = new CustomEvent(eventName, {detail,composed: !this.preventCompose,bubbles: !this.preventBubbles,cancelable: false,...options,})this.dispatchEvent(event)return event}// 判断 slot 是否传入内容hasSlot(name?: string) {if (name && name !== 'default') {return !![...this.childNodes].find(node => node.nodeType === node.ELEMENT_NODE && (node as Element).getAttribute('slot') === name)}return [...this.childNodes].some(node => {if (node.nodeType === node.TEXT_NODE && !!node.textContent?.trim()) {return true}if (node.nodeType === node.ELEMENT_NODE) {const el = node as HTMLElementif (!el.hasAttribute('slot')) {return true}}return false})}// 各个生命周期// 挂载时connectedCallback() {super.connectedCallback()console.log('Custom element added to page.')// 第一次被插入文档时执行,跳过节点删除后又重新插入的情形if (!this.hasUpdated) {this.setAttribute('wc-component', '')this.setAttribute('wc-pending', '')}}// 卸载时disconnectedCallback() {super.disconnectedCallback()console.log('Custom element removed from page.')}// 移动到另一个文档的时候adoptedCallback() {console.log('Custom element moved.')}// 元素的属性被添加、删除或修改时调用attributeChangedCallback(name: string, oldValue: any, newValue: any) {super.attributeChangedCallback(name, oldValue, newValue)console.log(`Attribute ${name} has changed.`)}// 或使用静态属性代替get方法static get observedAttributes() {// 指定要监听的元素的属性数组// 对应的attr改变后,会触发attributeChangedCallback// return ['name', 'date']return []}// 是否应该更新protected shouldUpdate(_changedProperties: PropertyValues): boolean {return true}// 即将更新protected willUpdate(_changedProperties: PropertyValues): void {super.willUpdate(_changedProperties)console.log('willUpdate')}// 首次更新元素时调用。实现在更新后对元素执行一次性工作protected firstUpdated(changedProperties: PropertyValues) {super.firstUpdated(changedProperties)console.log('this.hasUpdated: ', this.hasUpdated)// this.requestUpdate()// 两帧数后执行requestAnimationFrame(() => {requestAnimationFrame(() => {this.removeAttribute('wc-pending')})})}protected updated(_changedProperties: PropertyValues): void {super.updated(_changedProperties)this.updateComplete.then((res) => {console.log('updateComplete', res)})}
}
withProperties封装
import type { LitElement, PropertyValues } from 'lit'type Constructor<T> = new (...args: any[]) => Texport default <T extends Constructor<LitElement>>(superClass: T) => {class WithPropertiesElement extends superClass {props: Record<string, any> = {}willUpdate(changedProperties: PropertyValues) {const obj = [...changedProperties.entries()].reduce<any>((obj, [key]) => ((obj[key] = (this as any)[key]), obj),{})this.props = { ...this.props, ...obj }super.willUpdate(changedProperties)}}return WithPropertiesElement as Constructor<{props: Record<string, any>}> & T
}
这段代码定义了一个高阶组件(Higher-Order Component,HOC),用于增强 LitElement 组件的功能。具体来说,它的作用是:
-
创建一个带有额外属性管理功能的组件类:
- 通过扩展传入的基类(比如
LitElement
或其子类),添加一个props
属性来存储组件的属性值。
- 通过扩展传入的基类(比如
-
在组件更新前处理属性变化:
- 重写
willUpdate
生命周期方法,这个方法在组件的属性发生变化并且组件即将更新之前被调用。
- 重写
-
收集并存储属性变化:
- 使用
changedProperties
对象(一个 Map 类型的对象,包含属性名和属性变化的信息)来收集属性的变化。 - 将变化的属性存储到
this.props
对象中,这样可以通过props
属性访问组件的所有属性值。
- 使用
-
保持基类的
willUpdate
方法的调用:- 调用
super.willUpdate(changedProperties)
以确保基类的willUpdate
方法也能正常执行。
- 调用
代码详解
- 定义了一个默认导出的函数,它接受一个构造函数
superClass
(应该是LitElement
或其子类的构造函数)。 - 创建一个新类
WithPropertiesElement
,继承自superClass
。 - 在
WithPropertiesElement
类中定义了一个props
属性,用于存储属性值。 - 重写
willUpdate
方法,在组件更新前处理属性变化,并将变化的属性存储到this.props
中。 - 返回
WithPropertiesElement
类,并通过类型断言确保它具有额外的props
属性。
使用示例
假设你有一个基础的 LitElement 组件:
import { LitElement, html } from 'lit';class MyElement extends LitElement {count = 0;render() {return html`<p>Count: ${this.count}</p>`;}
}customElements.define('my-element', MyElement);
你可以使用这个高阶组件来增强它:
import { WithPropertiesElement } from './WithPropertiesElement';
import { LitElement, html } from 'lit';const EnhancedElement = WithPropertiesElement(MyElement);customElements.define('enhanced-element', EnhancedElement);const element = new EnhancedElement();
document.body.appendChild(element);console.log(element.props); // { count: 0 }
在这个示例中,EnhancedElement
继承自 MyElement
并添加了属性管理功能。可以通过 element.props
访问组件的所有属性值。
这种模式在需要在组件中统一管理属性或在组件更新前进行额外处理时非常有用。
存放React组件的webcomponent基类
重头戏来了
import { ChildPart, html, PropertyValues } from 'lit'
import { query } from 'lit/decorators.js'
import { Fragment, createElement as h } from 'react'
import ReactDOM from 'react-dom'
import withProperties from '../mixin/withProperties'
import LightBase from './Base'type H = typeof hconst Root: React.FC<any> = props => {return h(Fragment, {...props,})
}const omit = (obj: Record<string, any>, filter: string[] = []) =>Object.fromEntries(Object.entries(obj).filter(([key]) => !filter.includes(key)))// React组件基类
export default class extends withProperties(LightBase) {// 子类要重写这个方法来渲染自己的组件protected renderReact(h: H): React.ReactNode {return null}protected customContainer(): Element | undefined {return this.$reactRoot}protected getReactProps(props: Record<string, any>) {return omit(props, ['preventCompose', 'preventBubbles', 'localeMessages'])}protected extraStyle = ''@query('.react-root')$reactRoot?: HTMLElementupdated(changed: PropertyValues) {super.updated(changed)this.doRender()}connectedCallback() {super.connectedCallback()// 节点删除后重新插入的情形if (this.hasUpdated) {this.doRender()}}disconnectedCallback() {super.disconnectedCallback()this.doUnmount()}private container?: Elementprivate doRender() {const container = this.customContainer()if (!container) {this.doUnmount() // 卸载前一次渲染的内容} else {this.container = containerReactDOM.render(h(Root, {}, this.renderReact(h)), container, () => {// hack for error: https://github.com/lit/lit/blob/f8ee010bc515e4bb319e98408d38ef3d971cc08b/packages/lit-html/src/lit-html.ts#L1122// 在React中使用此组件且非首次更新时会报错,因为lit默认会在组件下创建一个注释节点,更新时会对这个节点进行操作,而React渲染时把这个注释节点干掉了,这里要把他加回去const childPart = (this as any).__childPart as ChildPart | undefinedchildPart?.startNode && this.appendChild(childPart.startNode)})}}private doUnmount() {if (this.container) {ReactDOM.unmountComponentAtNode(this.container)}}render() {return html` <div class="react-root"></div> `}
}
使用Demo
import { unsafeCSS } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import ReactBase from './ReactBase'// 自己的React组件
import Component from './Component'import style from './index.less?inline'@customElement('my-diy-react-wc')
export default class DataReport extends ReactBase {static get styles() {return unsafeCSS([style])}/*** 自定义属性*/@property()language: string = 'zh-CN'// ReactBase中用来渲染React,不要删除renderReact() {return <Component language={this.language} />}
}
参考文章
https://juejin.cn/post/7296850940404580364?searchId=2024071620331848BC966F0D2051B9C533#heading-9
lit官网:https://lit.dev/docs/components/styles/
webcomponent文档:https://developer.mozilla.org/en-US/docs/Web/API/Web_components
相关文章:
[React]利用Webcomponent封装React组件
[React]利用Webcomponent封装React组件 为什么这么做 我个人认为,最重要的点是可以很方便地跨框架挂载和卸载wc元素(至少我在项目里是这么玩的),此外,基于wc的css沙箱以及它的shadowRoot机制,可以提供一套…...
Linux C服务需要在A服务和B服务都启动成功后才能启动
需求 C服务需要在A服务和B服务都启动成功后才能启动 服务编号服务名服务Anginx.service服务Bmashang.service服务Credis.service 实验 如果您想要 redis.service 在 nginx.service 和 mashang.service 都成功启动后才能启动,那么需要在 redis.service 的服务单元…...
VSCODE 下 openocd Jlink 的配置笔记
title: VSCODE 下 openocd Jlink 的配置笔记 tags: STM32HalCubemax 文章目录 内容VSCODE 下 openocd Jlink 的配置笔记安装完成后修改jlink的配置文件然后修改你的下载器为jlink烧录你的项目绝对会出现下面的问题那么打开下载的第一个软件 (点到这个jlink右键&…...
JVM--HostSpot算法细节实现
1.根节点枚举 定义: 我们以可达性分析算法中从GC Roots 集合找引用链这个操作作为介绍虚拟机高效实现的第一个例 子。固定可作为GC Roots 的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如 栈帧中的本地变量表&a…...
【Unity实战100例】Unity声音可视化多种显示效果
目录 一、技术背景 二、界面搭建 三、 实现 UIAudioVisualizer 基类 四、实现 AudioSampler 类 五、实现 IAudioSample 接口 六、实现MusicAudioVisualizer 七、实现 MicrophoneAudioManager 类 八、实现 MicrophoneAudioVisualizer 类 九、源码下载 Unity声音可视化四…...
[Cesium for Supermap] 加载3dTiles,点击获取属性
代码: // 设为椭球var obj [6378137.0, 6378137.0, 6356752.3142451793];Cesium.Ellipsoid.WGS84 Object.freeze(new Cesium.Ellipsoid(obj[0], obj[1], obj[2]));var viewer new Cesium.Viewer(cesiumContainer);var scene viewer.scenescene.lightSource.ambi…...
【stm32项目】基于stm32智能宠物喂养(完整工程资料源码)
基于STM32宠物喂养系统 前言: 随着人们生活幸福指数的提高,越来越多的家庭选择养宠物来为生活增添乐趣。然而,由于工作等原因,许多主人无法及时为宠物提供充足的食物与水。为了解决这一问题,我设计了一款便捷的宠物喂…...
选择Maya进行3D动画制作与渲染的理由
如果你对3D动画充满热情并追求成为专业3D动画师的梦想,你一定听说过Maya——近年来3D动画的行业标准。Maya被3D艺术家广泛使用,你是否想知道为什么Maya总是他们的首选?下面一起来了解下。 一、什么是Maya? 由Autodesk开发的Maya是…...
Promise应用
创建一个 Promise 对象 let promise showLabelText() {return new Promise((resolve, reject) > {axios({method: "post",url: "/code/expose/interface/queryActionPlan",data: { situationOneId: 19999, labels: [1, 2, 3] }}).then(response > {…...
51单片机嵌入式开发:13、STC89C52RC 之 RS232与电脑通讯
STC89C52RC 之 RS232与电脑通讯 第十三节课,RS232与电脑通讯1 概述2 Uart介绍2.1 概述2.2 STC89C52UART介绍2.3 STC89C52 UART寄存器介绍2.4 STC89C52 UART操作 3 C51 UART总结 第十三节课,RS232与电脑通讯 1 概述 RS232(Recommended Stand…...
当代政治制度(练习题)
当代政治制度(练习题) Rz整理 仅供参考 干部鉴定必须坚持德才兼备原则,考核内容包括(A.德 B.廉 C.能 D.勤 F.绩 ) A.德 B.廉 C.能 D.勤 E.信 F.绩我国干部任用的方式主要包括(A.考任 C.委任 D.聘任 F.选任…...
前端pc和小程序接入快递100(跳转方式和api方式)====实时查询接口
文章目录 跳转方式微信小程序(我以uniapp为例)pc api接入说明关于签名计算成功示例 跳转方式 没有任何开发成本,直接一键接入 可以直接看官方文档 https://www.kuaidi100.com/openapi/api_wxmp.shtml 微信小程序(我以uniapp为例…...
电脑永久性不小心删除了东西还可以恢复吗 电脑提示永久性删除文件怎么找回 怎么恢复电脑永久删除的数据
永久删除电脑数据的操作,对于很多常用电脑设备的用户来说,可以说时有发生!但是,因为这些情况大都发生在不经意间,所以每每让广大用户感觉到十分苦恼。永久删除也有后悔药,轻松找回电脑中误删的文件。恢复文…...
LeetCode热题100刷题16:74. 搜索二维矩阵、33. 搜索旋转排序数组、153. 寻找旋转排序数组中的最小值、98. 验证二叉搜索树
74. 搜索二维矩阵 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int row matrix.size();int col matrix[0].size();for(int i0;i<row;i) {//先排除一下不存在的情况if(i>0&&matrix[i][0]>target…...
C++仿函数
在C中,我们经常需要对类中的元素进行比较,例如在排序、查找等操作中。为了使类更加灵活,我们可以通过自定义比较函数来实现不同的比较方式。在本文中,我们将探讨如何在类中使用仿函数和 Lambda 表达式来定义自定义比较函数。 1. …...
文献阅读:tidyomics 生态系统:增强组学数据分析
文献介绍 文献题目: The tidyomics ecosystem: enhancing omic data analyses 研究团队: Stefano Mangiola(澳大利亚沃尔特和伊丽莎霍尔医学研究所)、Michael I. Love(美国北卡罗来纳大学教堂山分校)、Ant…...
MySQL运维实战之Clone插件(10.1)使用Clone插件
作者:俊达 clone插件介绍 mysql 8.0.17版本引入了clone插件。使用clone插件可以对本地l或远程的mysql实例进行clone操作。clone插件会拷贝innodb存储引擎表,clone得到的是原数据库的一个一致性的快照,可以使用该快照数据来启动新的实例。cl…...
【系统架构设计】数据库系统(三)
数据库系统(三) 数据库模式与范式数据库设计备份与恢复分布式数据库系统分布式数据库的概念特点分类目标 分布式数据库的架构分布式数据库系统与并行数据库系统 数据仓库数据挖掘NoSQL大数据 数据库模式与范式 数据库设计 备份与恢复 分布式数据库系统…...
免费视频批量横版转竖版
简介 视频处理器 v1.3 是一款由是貔貅呀开发的视频编辑和处理工具,提供高效便捷的视频批量横转竖,主要功能: 导入与删除文件:轻松导入多个视频文件,删除不必要的文件。暂停与继续处理:随时暂停和继续处理。…...
内存管理(知识点)
c语言与c对于内存管理的区别 c语言中动态内存管理方式 malloc\calloc\realloc\free c内存管理方式 c兼容c语言所以c语言中的方式也可以使用 但是用起来太麻烦 所以通过new和delete操作符竞选动态内存管理 单个对象 c语言: int* p2(int*)malloc(sizeof(int)) c : int*p3 new…...
【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【29】Sentinel
持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【29】Sentinel 简介熔断降级什么是熔断什么是降级相同点不同点 整合Sentinel自定义sentinel流控返回数据使用Sentinel来保护feign远程调用自定义资源给网关整合Sentinel参考 简介 熔断降…...
防范UDP Flood攻击的策略与实践
UDP Flood攻击是一种常见的分布式拒绝服务(DDoS)攻击手段,通过向目标服务器发送大量无效的UDP数据包,消耗其网络带宽和处理资源,最终导致合法的网络服务无法正常运行。本文将深入探讨UDP Flood攻击的原理、常见的防御策…...
昇思25天学习打卡营第17天 | CycleGAN图像风格迁移互换
通过深入学习CycleGAN模型,我对无监督图像到图像的转换技术有了更深的理解。CycleGAN不仅能在没有成对训练样本的情况下实现域之间的转换,而且在保持内容结构的同时成功转换图像风格,这在许多应用中都非常有用,如艺术风格转换、季…...
Leetcode 2520. 统计能整除数字的位数
问题描述: 给你一个整数 num ,返回 num 中能整除 num 的数位的数目。 如果满足 nums % val 0 ,则认为整数 val 可以整除 nums 。 示例 1: 输入:num 7 输出:1 解释:7 被自己整除࿰…...
WEB前端08-综合案例(动态表格)
使用 HTML、CSS 和 JavaScript 创建动态表格 在本教程中,我们将创建一个动态表格,允许用户添加行、选择项目,并执行批量操作,如全选或删除选中的行。我们将通过 HTML、CSS 和 JavaScript 来实现这一功能。让我们逐步了解每个部分…...
【面试题】Redo log和Undo log
Redo log 介绍Redo log之前我们需要了解一下,mysql数据操作的流程: 上述就是数据操作的流程图,可以发现sql语句并不是直接操作的磁盘而是通过操作内存,然后进行内存到磁盘的一个同步。这里我们必须要了解一些区域: 缓…...
开发实战经验分享:互联网医院系统源码与在线问诊APP搭建
作为一名软件开发者,笔者有幸参与了多个互联网医院系统的开发项目,并在此过程中积累了丰富的实战经验。本文将结合我的开发经验,分享互联网医院系统源码的设计与在线问诊APP的搭建过程。 一、需求分析 在开发任何系统之前,首先要…...
springboot系列教程(十六):配置Actuator组件,实现系统监控
一、Actuator简介 1、监控组件作用 在生产环境中,需要实时或定期监控服务的可用性。Spring Boot的actuator(健康监控)功能提供了很多监控所需的接口,可以对应用系统进行配置查看、相关功能统计等。 2、监控分类 Actuator 提供…...
单臂路由组网实验,单臂路由的定义、适用情况、作用
一、定义 单臂路由是指通过在路由器的一个接口上配置许多子接口,从而实现原来相互隔离的不同VLAN之间的互通。 子接口:把路由器上的实际的物理接口划分为多个逻辑上的接口,这些被划分的逻辑接口就是子接口。 二、适用情况 用在没有三层交换机,却要实现不同VLAN之间的互…...
【数据结构初阶】顺序表三道经典算法题(详解+图例)
Hello!很高兴又见到你了~~~ 看看今天要学点什么来充实大脑吧—— 目录 1、移除元素 【思路图解】 【总结】 2、删除有序数组中的重复项 【思路图解】 【总结】 3、合并两个有序数组 【思路图解】 【总结】 至此结束,Show Time! 1、…...
深圳网站建设10086/qq群引流推广平台免费
我们踩着的只是两个脚印,我们看到的只是四角的天空。究竟是什么决定了我们能不能找到工作,找到什么样的工作。以下引用的文献,皆来自《社会学与中国社会》中的收录。这些被收录在这里的文献,好像是作者专门为收集到此书时加工的&a…...
做网站需求文档/南宁整合推广公司
代码如下2113: 1). xml可能的中文编码错误处理 def xml_Error_C(filename): fp_xmlopen(filename) fp_x#中文乱5261码改正 for i in range(os.path.getsize(filename)): i1 afp_xml.read(1) if a&: fp_xml.seek(-1,1) if fp_xml.read(6) : i5 continue else: fp…...
百度seo和sem/优化营商环境指什么
有两种解决方案: 1. 客户端button一定得是button类型,而不能是submit,且此服务器端的button只能用width0 height0 来隐藏,而不能用style"display:none;" 或visiblefalse来进行隐藏! 2. 在服务器上注册:this.btnStat.At…...
海南工程建设资料备案网站/市场调研表模板
转自:http://www.scrumcn.com/agile/scrum-knowledge-library/scrum.html SCRUM 是一个用于开发和维护复杂产品的框架 Scrum 是一个用于开发和维护复杂产品的框架 ,是一个增量的、迭代的开发过程。在这个框架中,整个开发过程由若干个短的迭代…...
门户网站建设 请示/外贸网站建设流程
黄河科技学院课程编号代码设置及说明.doc黄河科技学院课程编号代码设置及说明为了配合专业培养计划的修订工作,便于计算机管理,现将我校所开的全部课程(包括必修课、选修课、辅修专业所开课程以及课外培养项目)进行统一编号,具体编号要求和说…...
知名网络公司有哪些/济宁seo推广
先打出nand表0 nand 011 nand 100 nand 111 nand 01容易发现(!a)a nand a然后(a&b)!(a nand b)然后(a|b)!((!a)&(!b))然后(a^b)(a|b)&(a nand b)所以通过nand我们可以实现任意一种位运算所以每一位我们想得到0/1都是可以的按道理[L,R]中符合位数要求的数都能得到然…...