2021版小程序开发4——基础加强

2021版小程序开发4——基础加强

学习笔记 2025

  • 自定义组件
  • 组件中behaviors的作用
  • 安装和使用vant-weapp组件库
  • 使用MobX实现全局数据共享
  • 对小程序的API进行Promise化

具体的内容还包括:使用npm包、全局数据共享、分包和自定义tabBar的案例;

1 自定义组件

创建自定义组件

创建/components目录,继续创建子目录(目录名为组件名),右击子目录新建Component填入组件名即可;每个组件也会自动创建四个组成文件;

引入自定义组件:

  • 局部引用:在页面的json配置文件中配置
  • 全局引用:在全局的json配置文件中配置
{
  "usingComponents" : {
    'ms-test': "/components/test/test" // 组件路径 不要带后缀
  }
}

组件和页面的区别:组件的js和json文件 明显区别于页面的

  • 组件的json中,需要声明属性 "component":true
  • 组件的js中调用的是Component()函数,而页面的js中调用的是Page()函数;
  • 组件的事件处理函数需要定义到methods节点中,而页面的函数只需要定义在和data节点平级的节点中即可);

内部方法建议以下划线开头;

在组件和引用组件的页面中 尽量使用class选择器,不要使用id、属性、标签选择器;因为只有class选择器受样式隔离的影响;
全局/页面样式不会影响组件内样式;

修改组件样式的隔离选项

修改组件样式的隔离选项:

  • 自定义组件的样式隔离特性能够防止组件内外样式相互干扰;
  • 如果希望在外部能够影响组件内部的样式,可以修改组件的样式隔离选项:styleIsolation
    • isolated 默认值
    • apply-shared 页面可以影响组件
    • shared 页面和组件样式可相互影响,而且组件中样式还会影响其他设置了apply-shared或shared的组定义组件;
// 在组件js中配置
Component({
  options: {
    styleIsolation: 'isolated'
  }
})

// 或在组件.json中配置
{
  'styleIsolation': 'isolated'
}

自定义组件的数据、方法和属性

用于组件模版渲染的私有数据,需要定义到data节点中(也页面一样);

组件中,事件处理函数和自定义方法都需要定义到methods节点中;

组件中,properties是组件的对外属性,用来接受外界传递到组件的数据:

  • 使用组件时,通过属性传值;
{
  properties: {
    max:{
      type: Number, // 数据类型
      value: 10 // 默认值
    },
    min: Number // 无需指定默认值时的简化用法

  }
}

// <my-test max="11"></my-test>

和Vue比,比较特别的一点是,小程序组件中的properties和data数据都是可读可写的;

组件中的数据监听器

监听属性变化,执行特定操作;(类似Vue中watch)

小程序中监听的基本语法格式:

Component({
  // 需要声明一个 observers 配置对象
  observers: {
    // 可以在一个监听器中 同时监听多个字段的变化
    "字段1, 字段2": function("字段1的新值,字段2的新值"){
      // 触发方式:
      // 为字段1 赋值
      // 为字段2 赋值
      // setData为对象赋值
    }
  }
})

关于setData/observers:使用嵌套对象的子属性赋值/监听的方法;

data: {
  rgb: {
    r: 0,
    g: 0
  },
  fullColor: ''
}

...
this.setData({
  // 为嵌套对象的子属性赋值的方式
  'rgb.r': 255
})

...
observers: {
  'rgb.r, rgb.g' : function(r, g){
    this.setData(
      fullColor: `${r}, ${g}`
    )
  }
}

通配符监听:

  • 使用通配符**来监听对象中所有属性的变化;
observers: {
  'rgb.**': function(obj){
    this.setData({
      fullColor: `${obj.r}, ${obj.g}`
    })
  }
}

纯数据字段

指不用于界面 渲染的data字段(具体的:既不会展示在界面上,也不会传递给其他组件,仅在当前页面的内部使用);

这样的数据适合被定义为纯数据字段,有助于提升页面的更新性能;

使用:

  • 在Component的options节点中,指定pureDataPattern为一个正则表达式,符合这个正则的字段都将成为纯数据字段(可以是一个对象);
Component({
  options:{
    pureDataPattern: /^_/ // 以下划线开头的字段
  },
  data: {
    a: true,
    _b: true, // pureData
    _rgb: { // pureData
      r: 0,
      g: 0,
    }
  }

})

快捷选中: cmd+d

2 组件生命周期

  • created:刚创建
    • 不能调用setData,因为data还没初始化
    • 可以给this添加一些自定义的属性字段
  • attached: 刚被放入页面节点树,还没渲染
    • 可以发送网络请求;
  • ready:在视图层布局完成之后执行
  • moved:组件实例被移动到节点树的另一个位置时执行
  • detached:从页面节点树移除
    • 清理事件监听;
  • error:组件方法异常的时候执行

排列顺序也是执行顺序;

小程序组件的生命周期函数可以在lifetimes字段内进行声明;

Components({
  lifetimes:{
    attached(){},
    detached(){},
  }
})

组件所在页面的生命周期

在自定义组件的行为需要依赖页面状态的变化的时候,可能需要在组件中监听组件所在页面的生命周期;

组件能使用的页面生命周期函数:

  • 这些函数需要定义在pageLieftimes节点中;
  • show:组件所在页面被显示的时候
  • hide:组件所在页面被隐藏的时候
  • resize::组件所在页面的尺寸变化的时候
Components({
  pageLieftimes:{
    show(){
      Math.floor(Math.random() * 256) // 0~255
    },
    hide(){},
    resize(){},
  }
})

3 自定义组件插槽

  • 提供<slot></slot>节点,相当于占位符,用于承载组件使用者提供的wxml结构;
<!-- test组件内 -->
<view>
<!-- 单插槽 -->
  <slot></slot>
</view>

<!-- 使用组件时 -->
<test>
  <view>替换组件内占位符</view>
</test>

如果需要让组件支持多个插槽,只需要在options节点配置multipleSlots属性为true;每个slot占位以不同的name区分;

<!-- test组件内 -->
<view>
  <slot name="header"></slot>
  <view>Content</view>
  <slot name="footer"></slot>
</view>

<!-- 使用组件时 -->
<test>
  <view slot="header">替换组件内占位符header</view>
  <view slot="footer">替换组件内占位符footer</view>
</test>

4 父子组件间通信

父子组件间传值的三种方式:

  • 通过属性绑定传值,父向子传值,需要指定属性,仅能传递json兼容的数据(无法传递方法);
  • 事件绑定,子向父传值
    • 在父组件中定义一个回调函数,然后传递给子组件;
    • 在子组件中 调用this.triggerEvent('自定义事件名称', {参数对象}),将数据发送到父组件
    • 父组件js中,通过e.detail获取子组件所传递的数据,可以是任意数据类型(如方法);
  • 父组件中可通过 this.selectComponent()方法获取子组件实例,以直接访问子组件的任何数据和方法;

属性绑定:

  • 父组件中使用子组件并绑定数据(注意这里与vue不同,小程序的wxml中使用的是Mustache语法,如 <test title="{{use_title}}"></test>);
  • 子组件中properties节点声明相应的属性名;
  • 这个绑定是单向的,因此如果在子组件中title属性发生了变化,还需要使用子向父传值;

事件绑定:

<!-- 事件绑定 -->
<test bind:sync="syncCountChange"></test>
// 父组件
syncCountChange(e){
  e.detail.value
}

// 子组件
methods: {
  addCount(){
    this.setData({
      count: this.properties.count + 1
    })
    this.triggerEvent('sync', {value: this.properties.count})
  }
}

直接获取子组件实例:

  • this.selectComponent(#id或.class选择器)
<test class="customA"></test>
const childA = this.selectComponent(".customA")
// childA.setData(...)

5 自定义组件的behaviors

自定义组件的behaviors

  • 用于实现组件间代码共享的特性,类似vue中mixins
  • 每个组件可以引入多个behaviors,behaviors间也可以相互引用;

每个behavior中都可以包含一组:属性、数据、生命周期函数和方法,组件应用它时,它的属性、数据和方法会被合并到组件中;

创建 behavior

// 调用 Behavior() 可以创建一个共享的behavior对象
module.exports = Behavior({
  // 属性
  properties:{},
  // 数据
  data:{},
  // 方法
  methods:{}
  // 其他behavior
  behaviors:[],
  // 生命周期函数
  created(){},
  attached(){},
  ready(){},
  moved(){},
  detached(){},
})

导入并使用behavior

使用require()方法导入,挂载后即可访问其中数据或方法;

const myB = require("../../behaviors/my-behavior")

Component({
  // 挂载到 behaviors 数组节点中
  behaviors:[myB],
})

behaviors中同名字段的覆盖和组合规则:

  • 同名数据字段(data)
    • 对象重复 合并
    • 组件 > 父behavior > 子behavior
  • 同名属性(properties)或方法(methods)
  • 同名的生命周期函数

具体的可参考小程序官方文档;

6 使用npm包管理三方工具包

3个限制:

  • 不支持依赖于Node.js内置库的包;
  • 不支持依赖于浏览器内置对象的包;
  • 不支持依赖于C++插件的包;

服务于小程序的包没有那么多;虽然有三方包的加持,但是不要忘记小程序本身提供的丰富API;

Vant Weapp

一套开源的小程序UI组件库

  • https://youzan.github.io/vant-weapp

安装Vant组件库:

  • 通过npm安装 建议指定版本,如@1.3.3(具体可参考文档)
    • 在小程序文件目录空白区域,右击打开外部终端
    • 初始化npm(会创建一个package.json的配置文件): npm init -y
    • copy命令进行vant安装
  • 构建npm包:IDE 工具 构建npm 勾选使用,再本地设置 勾选使用npm模块(以使用三方包);
  • 修改app.json配置文件(不使用新版样式,以防止样式冲突)

使用Vant组件库:

// 全局配置使用组件
"usingComponents":{
  'van-button': "@vant/weapp/button/index"
}

定义Vant全局的主题样式:

  • 使用CSS变量(CSS_custom_properties)来实现,具体可参考MDN文档;
  • 在 app.wxss中,写入CSS变量,即可对全局生效;
  • 参考Vant定制主题,可以获取到可用的样式变量;
/* 为了全局 变量都定义在页面的根节点标签page中 */
page {
  --变量名: CSS变量值;
  @变量名: less变量值;
}

/* 使用时 */
CSS属性 : var(--变量名, 指定变量不存在时的默认值)

API Promise化处理

将基于回调函数的异步api,改造为基于Promise的异步API;

实现方式:

  • 依赖miniprogram-api-promise三方包,使用npm install --save miniprogram-api-promise@1.0.4(建议指定版本号)安装;
  • 每次构建前建议都删除依赖目录:miniprogram_npm目录
  • 重新构建:点击工具->构建,重新构建
    • 在node_modules目录下的包不可以直接使用,构建后,在miniprogram_npm目录下的包才可以使用;

使用:

// 在app.js中 按需导入
import {promisifyAll} form 'miniprogram-api-promise'

// 定义了一个空对象 之后就可以通过wx.p去调用promise化的api(wxp和wx.p是同一个对象)
const wxp = wx.p = {}
// 将wx的异步请求全部promise化 之后挂载到wxp上
promisifyAll(wx, wxp)
async getInfo(){
  // 解构data赋值给res
  const {data: res} = await wx.p.request({
    method: "GET",
    url: '',
    data: {}
  })
  console.log(res)
}

全局数据共享

即状态管理,是为了解决组件间数据共享的问题,类似Vue中如vuex

小程序中可使用两个包:mobx-miniprogream配合mobx-miniprogream-bindings实现全局数据共享

  • mobx-miniprogream 用于创建 store实例
  • mobx-miniprogream-bindings 用于把store中的数据共享方法 绑定到组件或页面中使用

npm安装,之后:

  • 创建一个store目录
  • 其中再创建一个 store.js,专门创建Store的实例对象
// store.js
import {observable,  action} form 'mobx-miniprogream'

// 按需导出
export const store = observable({
  // 挂载需要共享的数据
  // 数据
  num1: 1,
  num2: 1,
  // 计算属性
  get sum(){
    return this.num1 + this.num2
  },
  // actions
  // 专门用来定义 修改数据的方法
  dosomething: action(function (step){
    this.num1 += step
  })
})
  • 将Store中的成员绑定到页面中
import { createStoreBindings } from 'mobx-miniprogream-bindings'
import {store} from "../../store/store"

Page({
  onLoad: function(){
    // 
    this.storeBindings = createStoreBindings(this, {
      store,
      fields: ['num1', 'num2', 'sum'], // 绑定的字段
      actions: ['dosomething'] // 绑定的方法
    })
  },
  onUnload: function() {
    this.storeBindings.destroyStoreBindings()
  }
})

组件中的绑定略有不同:

import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'

Component({
  behaviors: [storeBindingsBehavior],
  // 配置对象 storeBindings
  storeBindings: {
    store,
    fields: {
      numA: (store) => store.num1,
      numB: 'num2'
    },
    actions: {
      dosome: 'dosomething'
    }
  }
})

7 分包

指把一个完整的小程序项目,按需求划分为不同的子包,在构建时打包成不同的分包,用户在使用的时候按需加载;

默认是不分包的;

一个主包+多个分包:

  • 主包:一般包含启动页面或多个TabBar页面,以及所有分包都要用到的一些公共资源;
  • 分包:只当前分包相关的页面和私有资源;

分包可以引用主包内的公共资源;

分包加载:

  • 小程序启动时,默认下载主包并启动主包内页面;
    • tabBar只能放到主包中;
  • 当进入分包某页面时,才会下载对应分包;

分包体积限制:

  • 所有分包不超过16M
  • 单个包不超过2M

配置分包:

  • 项目目录解构的变化;
    • 主包: pages
    • 分包A: packageA/pages
    • 分包B: packageB/pages
  • app.json配置节点(修改后保存会自动创建文件目录);
    • pages
    • subpackages
"pages": [ "pages/index/index", "pages/logs/logs"],
"subpackages":[{
  "root": "packageA", // 分包A的根目录
  "pages": ["pages/indexA/indexA", "pages/logsA/logsA"] // 相对当前root的页面存放路径
},{
  "root": "packageB",
  "name": "pkg2", // 分包别名
  "pages": ["pages/indexB/indexB", "pages/logsB/logsB"]
}]

在项目基本信息面板,可以看到各个分包的体积;

打包原则:

  • 按subpackages配置进行分包,之外的目录会被打包到主包中;
  • 主包有自己的pages(即最外层的pages);
  • tabBar必须在主包内;
  • 分包之间不能相互嵌套;

独立分包:

  • 一种特殊的分包,只不过可以独立于主包二单独运行;
    • 正常分包,只能通过主包跳转(需要先下载主包);
    • 而独立分包,可以在小程序启动后直接载入(无需依赖主包);
  • 一个小程序中可以有多个独立分包;
  • 独立分包与主包(包括主包内的公共资源)和其他分包(包括独立分包)间是隔绝的,不能相互引用彼此资源;

配置独立分包:

  • 在分包配置中增加"independent": true即可;

分包预下载:

  • 在进入指定页面时可以配置触发该行为,将可能使用的后续分包页面提前下载;
  • 使用preloadRule节点进行定义;
"preloadRule": {
  "pages/myself/myself": {
    // 在进入 myself页面的时候 触发加载以下分包
    "network": "all", // 网络模式 默认为wifi
    "packages": ["pkg2"] // 分包别名
  }
}

同一个主/分包中的页面享用共同的预下载大小限额2M,如主包的tabBar三个页面分别预下载了体积为1M的分包,此时主包就会下载3M的分包,这是不允许的(会失败);

8 自定义tabBar实现更丰富的功能

  • 自定义组件:参考小程序官方文档,自定义tabBar(注意这是一个组件)
  • Vant组件库:提供了一个可用的Tabbar
    • 切换tab,拿到change的tab索引,调用wx.switchTab方法;
    • 把tab索引也放到store中(包括相应的修改值的方法),以解决索引无法切换的问题;
  • MobX数据共享:参考小程序官方文档,扩展能力,框架扩展;
  • 组件样式隔离:要覆盖tabBar的默认样式,需要修改自定义Tabbar组件样式隔离选项的值,
  • 组件数据监听器:监听Store中的字段,为组件内变量赋值
  • 组件的behaviors
  • Vant样式覆盖:参考Vant文档,样式覆盖
observers: {
  'sum': function(val){
    this.setData({
      // 为datalist数组下标为1的对象的info属性赋值
      'datalist[1].info': val
    })
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Node.js下载安装及环境配置教程 (详细版)

Node.js&#xff1a;是一个基于 Chrome V8 引擎的 JavaScript 运行时&#xff0c;用于构建可扩展的网络应用程序。Node.js 使用事件驱动、非阻塞 I/O 模型&#xff0c;使其非常适合构建实时应用程序。 Node.js 提供了一种轻量、高效、可扩展的方式来构建网络应用程序&#xff0…

ProfiNet转CANopen应用于汽车总装生产线输送设备ProfiNet与草棚CANopen质量检测系统

ProfiNet转CANopen协议转换网关模块&#xff0c;广泛应用于汽车行业。可替代NT 100-RE-CO和AB7658/7307产品功能 项目概述 在汽车总装生产线的末尾环节&#xff0c;汽车总装生产线输送设备起着关键的搬运作用&#xff0c;其基于 ProfiNet 协议运行&#xff0c;精准控制车辆在各…

「全网最细 + 实战源码案例」设计模式——桥接模式

核心思想 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;将抽象部分与其实现部分分离&#xff0c;使它们可以独立变化。降低代码耦合度&#xff0c;避免类爆炸&#xff0c;提高代码的可扩展性。 结构 1. Implementation&#xff08;实现类…

Attention--人工智能领域的核心技术

1. Attention 的全称与基本概念 在人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;领域&#xff0c;Attention 机制的全称是 Attention Mechanism&#xff08;注意力机制&#xff09;。它是一种能够动态分配计算资源&#xff0c;使模型在处理输入数据…

DeepSeek能执行程序吗?

1. 前言 大过年的&#xff0c;继续蹭DeepSeek的热点&#xff0c;前面考察了DeepSeek能否进行推理&#xff08;DeekSeek能否进行逻辑推理&#xff09;&#xff0c;其实似乎没有结论&#xff0c;因为还没有到上难度&#xff0c;DeepSeek似乎就纠结在一些与推理无关的事情上了&am…

5.3.2 软件设计原则

文章目录 抽象模块化信息隐蔽与独立性衡量 软件设计原则&#xff1a;抽象、模块化、信息隐蔽。 抽象 抽象是抽出事物本质的共同特性。过程抽象是指将一个明确定义功能的操作当作单个实体看待。数据抽象是对数据的类型、操作、取值范围进行定义&#xff0c;然后通过这些操作对数…

STM32 TIM编码器接口测速

编码器接口简介&#xff1a; Encoder Interface 编码器接口 编码器接口可接收增量&#xff08;正交&#xff09;编码器的信号&#xff0c;根据编码器旋转产生的正交信号脉冲&#xff0c;自动控制CNT自增或自减&#xff0c;从而指示编码器的位置、旋转方向和旋转速度 每个高级定…

四.4 Redis 五大数据类型/结构的详细说明/详细使用( zset 有序集合数据类型详解和使用)

四.4 Redis 五大数据类型/结构的详细说明/详细使用&#xff08; zset 有序集合数据类型详解和使用&#xff09; 文章目录 四.4 Redis 五大数据类型/结构的详细说明/详细使用&#xff08; zset 有序集合数据类型详解和使用&#xff09;1. 有序集合 Zset(sorted set)2. zset 有序…

Spring AI 在微服务中的应用:支持分布式 AI 推理

1. 引言 在现代企业中&#xff0c;微服务架构 已成为开发复杂系统的主流方式&#xff0c;而 AI 模型推理 也越来越多地被集成到业务流程中。如何在分布式微服务架构下高效地集成 Spring AI&#xff0c;使多个服务可以协同完成 AI 任务&#xff0c;并支持分布式 AI 推理&#x…

使用Ollama和Open WebUI快速玩转大模型:简单快捷的尝试各种llm大模型,比如DeepSeek r1

Ollama本身就是非常优秀的大模型管理和推理组件&#xff0c;再使用Open WebUI更加如虎添翼&#xff01; Ollama快速使用指南 安装Ollama Windows下安装 下载Windows版Ollama软件&#xff1a;Release v0.5.7 ollama/ollama GitHub 下载ollama-windows-amd64.zip这个文件即可…

EasyExcel写入和读取多个sheet

最近在工作中&#xff0c;作者频频接触到Excel处理&#xff0c;因此也对EasyExcel进行了一定的研究和学习&#xff0c;也曾困扰过如何处理多个sheet&#xff0c;因此此处分享给大家&#xff0c;希望能有所帮助 目录 1.依赖 2. Excel类 3.处理Excel读取和写入多个sheet 4. 执…

《DeepSeek 网页/API 性能异常(DeepSeek Web/API Degraded Performance):网络安全日志》

DeepSeek 网页/API 性能异常&#xff08;DeepSeek Web/API Degraded Performance&#xff09;订阅 已识别 - 已识别问题&#xff0c;并且正在实施修复。 1月 29&#xff0c; 2025 - 20&#xff1a;57 CST 更新 - 我们将继续监控任何其他问题。 1月 28&#xff0c; 2025 - 22&am…

安卓(android)饭堂广播【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.熟悉广播机制的实现流程。 2.掌握广播接收者的创建方式。 3.掌握广播的类型以及自定义官博的创建。 二、实验条件 熟悉广播机制、广播接收者的概念、广播接收者的创建方式、自定广播实现方式以及有…

分享|借鉴传统操作系统中分层内存系统的理念(虚拟上下文管理技术)提升LLMs在长上下文中的表现

《MemGPT: Towards LLMs as Operating Systems》 结论&#xff1a; 大语言模型&#xff08;LLMs&#xff09;上下文窗口受限问题的背景下&#xff0c; 提出了 MemGPT&#xff0c;通过类操作系统的分层内存系统的虚拟上下文管理技术&#xff0c; 提升 LLMs 在复杂人物&#…

games101-作业3

由于此次试验需要加载模型&#xff0c;涉及到本地环节&#xff0c;如果是windows系统&#xff0c;需要对main函数中的路径稍作改变&#xff1a; 这么写需要&#xff1a; #include "windows.h" 该段代码&#xff1a; #include "windows.h" int main(int ar…

Spring Boot 日志:项目的“行车记录仪”

一、什么是Spring Boot日志 &#xff08;一&#xff09;日志引入 在正式介绍日志之前&#xff0c;我们先来看看上篇文章中&#xff08;Spring Boot 配置文件&#xff09;中的验证码功能的一个代码片段&#xff1a; 这是一段校验用户输入的验证码是否正确的后端代码&#xff0c…

【大厂AI实践】OPPO:大规模知识图谱及其在小布助手中的应用

导读&#xff1a;OPPO知识图谱是OPPO数智工程系统小布助手团队主导、多团队协作建设的自研大规模通用知识图谱&#xff0c;目前已达到数亿实体和数十亿三元组的规模&#xff0c;主要落地在小布助手知识问答、电商搜索等场景。 本文主要分享OPPO知识图谱建设过程中算法相关的技…

机器学习周报-文献阅读

文章目录 摘要Abstract 1 相关知识1.1 WDN建模1.2 掩码操作&#xff08;Masking Operation&#xff09; 2 论文内容2.1 WDN信息的数据处理2.2 使用所收集的数据构造模型2.2.1 Gated graph neural network2.2.2 Masking operation2.2.3 Training loss2.2.4 Evaluation metrics 2…

工具的应用——安装copilot

一、介绍Copilot copilot是一个AI辅助编程的助手&#xff0c;作为需要拥抱AI的程序员可以从此尝试进入&#xff0c;至于好与不好&#xff0c;应当是小马过河&#xff0c;各有各的心得。这里不做评述。重点在安装copilot的过程中遇到了一些问题&#xff0c;然后把它总结下&…

后盾人JS--闭包明明白白

延伸函数环境生命周期 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> <…