StencilJs 学习之 JSX

简介: Stencil 组件使用 JSX` 渲染,这是一种流行的声明式模板语法。每个组件都有一个渲染函数,它返回在运行时渲染到 DOM 的组件树。

Stencil 组件使用 JSX 渲染,这是一种流行的声明式模板语法。每个组件都有一个渲染函数,它返回在运行时渲染到 DOM 的组件树。

基础用法

render 函数用于输出将绘制到屏幕上的组件树。

class MyComponent {
   
    render() {
   
        return (
            <div>
                <h1>Hello World</h1>
                <p>This is JSX!</p>
            </div>
        );
    }
}

在这个例子中,我们返回了一个 div ,它有两个子元素 h1p

Host Element

如果你想修改宿主元素本身,比如向组件本身添加一个类或一个属性,可以使用 Host 组件。

数据绑定

组件经常需要渲染动态数据。要在 JSX 中执行此操作,请使用 {}

render() {
   
  return (
    <div>Hello {
   this.name}</div>
  )
}

如果你熟悉 ES6 模板变量,JSX 变量和 ES6 非常相似,只是没有 $ 符号。

//ES6
`Hello ${
     this.name}`

//JSX
Hello {
   this.name}

条件

如果你想根据不同的条件渲染不同的内容,可以使用 if/else 来实现

render() {
   
  if (this.name) {
   
    return ( <div>Hello {
   this.name}</div> )
  } else {
   
    return ( <div>Hello, World</div> )
  }
}

此外,可以使用 JavaScript 的三元运算符来创建内联条件

render() {
   
  return (
    <div>
    {
   this.name ? <p>Hello {
   this.name}</p> : <p>Hello World</p> }
    </div>
  );
}

请注意:Stencil 重用 DOM 元素以获得更好的性能。请看下面的代码:

{
   
    someCondition ? (
        <my-counter initialValue={
   2} />
    ) : (
        <my-counter initialValue={
   5} />
    );
}

上面的代码与下面的代码完全相同:

<my-counter initialValue={
   someCondition ? 2 : 5} />

因此,如果某些条件发生了变化,my-counter的内部状态不会被重置,它的生命周期方法(如 componentWillLoad())也不会被触发。相反,条件语句只会触发同一个组件的更新。

如果你想在一个条件语句中销毁并重新创建一个组件,你可以指定 key 属性。这告诉 Stencil,这些组件实际上是不同的兄弟组件:

{
   
    someCondition ? (
        <my-counter key="a" initialValue={
   2} />
    ) : (
        <my-counter key="b" initialValue={
   5} />
    );
}

这样,如果某些条件发生变化,你会得到一个新的 my-counter 组件实例,它具有新的内部状态,同时也将会同步运行生命周期 componentWillLoad()componentDidLoad()

Slots

组件通常需要在其组件树的特定位置动态渲染子组件,允许开发人员在使用我们的组件时提供子内容,我们的组件将子组件放置在适当的位置。

要做到这一点,您可以在 my-component 中使用 Slot 标签。

// my-component.tsx
render() {
   
  return (
    <div>
      <h2>A Component</h2>
      <div><slot /></div>
    </div>
  );
}

然后,如果用户在创建组件 my-component 时传递子组件,那么 my-component 将把该组件放在上面第二层的 div 中:

render(){
   
  return(
    <my-component>
      <p>Child Element</p>
    </my-component>
  )
}

slot 可以增加 name 属性,来决定内容的输出位置:

// my-component.tsx

render(){
   
  return [
    <slot name="item-start" />,
    <h1>Here is my main content</h1>,
    <slot name="item-end" />
  ]
}

render(){
   
  return(
    <my-component>
      <p slot="item-start">I'll be placed before the h1</p>
      <p slot="item-end">I'll be placed after the h1</p>
    </my-component>
  )
}

Dealing with Children

JSX 中节点的子节点在运行时对应于一个节点数组,无论它们是通过 array.prototype.map 跨数组创建的,还是直接在 JSX 中声明为兄弟节点。这意味着在运行时,下面两个顶级
div 的子元素(.Todo-one 和.todo-two)的表示方式相同:

render() {
   
  return (
    <>
      <div class="todo-one">
        {
   this.todos.map((todo) => (
          <span>{
    todo.taskName }</span>
        )}
      </div>
      <div class="todo-two">
        <span>{
    todos[0].taskName }</span>
        <span>{
    todos[1].taskName }</span>
      </div>
    </>
  )
}

如果这个子元素数组是动态的,即任何节点都可以被添加、删除或重新排序,那么最好为每个元素设置一个唯一的 key 属性,如下所示:

render() {
   
  return (
    <div>
      {
   this.todos.map((todo) =>
        <div key={
   todo.uid}>
          <div>{
   todo.taskName}</div>
        </div>
      )}
    </div>
  )
}

当子数组中的节点被重新排列时,Stencil 会努力在渲染时保留 DOM 节点,但它不能在所有情况下都这样做。设置一个 key 属性可以让 Stencil 确保在渲染时能够匹配新旧子节点,从而避免
不必要地重新创建 DOM 节点。

不要使用数组索引或其他非唯一值作为键。尝试确保每个子节点都有一个不变的 key,并且在其所有兄弟节点中是唯一的。

处理用户输入

Stencil 使用原生的 DOM 事件。

下面是一个处理按钮点击的例子。注意箭头函数的使用。

...
export class MyComponent {
   
  private handleClick = () => {
   
    alert('Received the button click!');
  }

  render() {
   
    return (
      <button onClick={
   this.handleClick}>Click Me!</button>
    );
  }
}

这是另一个监听输入变化的例子。注意箭头函数的使用。

...
export class MyComponent {
   
  private inputChanged = (event: Event) => {
   
    console.log('input changed: ', (event.target as HTMLInputElement).value);
  }

  render() {
   
    return (
      <input onChange={
   this.inputChanged}/>
    );
  }
}

复杂的模板内容(Complex Template Content)

到目前为止,我们已经看到了如何只返回一个根元素的例子。我们也可以在根元素中嵌套元素

在组件有多个“顶级”元素的情况下,render 函数可以返回一个数组。注意 div 元素。

render() {
   
  return ([
  // first top level element
  <div class="container">
    <ul>
      <li>Item 1</li>
      <li>Item 2</li>
      <li>Item 3</li>
    </ul>
  </div>,

  // second top level element, note the , above
  <div class="another-container">
    ... more html content ...
  </div>
  ]);
}

或者你可以使用 Fragment 函数组件,在这种情况下你不需要添加逗号:

import {
    Fragment } from '@stencil/core';
...
render() {
   
  return (<Fragment>
    // first top level element
    <div class="container">
      <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
      </ul>
    </div>

    <div class="another-container">
      ... more html content ...
    </div>
  </Fragment>);
}

也可以使用 innerHTML 直接将内容内联到元素中。例如,当动态加载一个 svg,然后想要在 div 中渲染它时,这就很有用了。这就像在普通的 HTML 中一样:

<div innerHTML={
   svgContent}></div>

获取 DOM 元素的引用

jsx 中使用 ref 属性来获取 dom 的引用,示例如下

@Component({
   
    tag: "app-home",
})
export class AppHome {
   
    textInput!: HTMLInputElement;

    handleSubmit = (event: Event) => {
   
        event.preventDefault();
        console.log(this.textInput.value);
    };

    render() {
   
        return (
            <form onSubmit={
   this.handleSubmit}>
                <label>
                    Name:
                    <input
                        type="text"
                        ref={
   (el) => (this.textInput = el as HTMLInputElement)}
                    />
                </label>
                <input type="submit" value="Submit" />
            </form>
        );
    }
}

避免共享 JSX 节点

在 jsx 中应该避免共享 jsx 节点,每一个 jsx 节点应该都是唯一的,这是因为在再次渲染时会遇到问题。

@Component({
   
  tag: 'my-cmp',
})
export class MyCmp {
   

  render() {
   
-    const sharedNode = <div>Text</div>;
    return (
      <div>
-        {
   sharedNode}
-        {
   sharedNode}
+        <div>Text</div>
+        <div>Text</div>
      </div>
    );
  }
}

或者,可以创建一个工厂函数来返回一个通用的 JSX 节点,因为返回值将是一个唯一的实例。 示例如下:

@Component({
   
    tag: "my-cmp",
})
export class MyCmp {
   
    getText() {
   
        return <div>Text</div>;
    }

    render() {
   
        return (
            <div>
                {
   this.getText()}
                {
   this.getText()}
            </div>
        );
    }
}

结束语

至此,我们已经基本把 StencilJs 的相关基础知识已经学习的差不多了,在下一个章节中将会使用之前学习到的知识来开发一个常用的组件。
由于我们只是使用 StencilJs 来开发 web component 组件,其它不想关的知识(router)便不再讲解。

相关文章
|
2天前
|
云安全 监控 安全
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
966 5
|
13天前
|
人工智能 Java API
Java 正式进入 Agentic AI 时代:Spring AI Alibaba 1.1 发布背后的技术演进
Spring AI Alibaba 1.1 正式发布,提供极简方式构建企业级AI智能体。基于ReactAgent核心,支持多智能体协作、上下文工程与生产级管控,助力开发者快速打造可靠、可扩展的智能应用。
1101 41
|
9天前
|
机器学习/深度学习 人工智能 数据可视化
1秒生图!6B参数如何“以小博大”生成超真实图像?
Z-Image是6B参数开源图像生成模型,仅需16GB显存即可生成媲美百亿级模型的超真实图像,支持中英双语文本渲染与智能编辑,登顶Hugging Face趋势榜,首日下载破50万。
673 39
|
13天前
|
人工智能 前端开发 算法
大厂CIO独家分享:AI如何重塑开发者未来十年
在 AI 时代,若你还在紧盯代码量、执着于全栈工程师的招聘,或者仅凭技术贡献率来评判价值,执着于业务提效的比例而忽略产研价值,你很可能已经被所谓的“常识”困住了脚步。
776 69
大厂CIO独家分享:AI如何重塑开发者未来十年
|
9天前
|
存储 自然语言处理 测试技术
一行代码,让 Elasticsearch 集群瞬间雪崩——5000W 数据压测下的性能避坑全攻略
本文深入剖析 Elasticsearch 中模糊查询的三大陷阱及性能优化方案。通过5000 万级数据量下做了高压测试,用真实数据复刻事故现场,助力开发者规避“查询雪崩”,为您的业务保驾护航。
479 30
|
16天前
|
数据采集 人工智能 自然语言处理
Meta SAM3开源:让图像分割,听懂你的话
Meta发布并开源SAM 3,首个支持文本或视觉提示的统一图像视频分割模型,可精准分割“红色条纹伞”等开放词汇概念,覆盖400万独特概念,性能达人类水平75%–80%,推动视觉分割新突破。
944 59
Meta SAM3开源:让图像分割,听懂你的话
|
6天前
|
弹性计算 网络协议 Linux
阿里云ECS云服务器详细新手购买流程步骤(图文详解)
新手怎么购买阿里云服务器ECS?今天出一期阿里云服务器ECS自定义购买流程:图文全解析,阿里云服务器ECS购买流程图解,自定义购买ECS的设置选项是最复杂的,以自定义购买云服务器ECS为例,包括付费类型、地域、网络及可用区、实例、镜像、系统盘、数据盘、公网IP、安全组及登录凭证详细设置教程:
205 114