✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

🐝 重要出现频率高

■ 基础

鸿蒙基于前端的typescript所以也会问到   当然还有很多 这里列举了较为频率高的。

CSS选择器 🐝

CSS选择器优先级

权重顺序: !important > 行内样式 > ID > 类、伪类、属性 > 标签 > 通配符 > 继承 > 浏览器默认属性

权重大小

通配符0
标签 1
类/伪类/属性 10
ID 100
行内样式 1000
important 无穷大

CSS3新特性 🐝

  • 属性选择器选择器
  • 圆角
  • 阴影
  • 渐变
  • 多列布局
  • css自定艺术性
  • 等等


 

谈谈你对盒模型的理解 🐝

问:什么是盒模型?

网页布局中CSS布局的思想模型,
主要规定元素和元素之间的关系
例如:内容(content),内边距(padding),边框(border),外边距(margin)

问: 标准盒模型、怪异盒模型

用box-sizing告诉浏览器如何计算一个元素是总宽度和总高度

标准盒模型   box-sizing: content-box
一个块的总宽度  width + margin(左右) + padding(左右) + border(左右)
  
IE盒模型或怪异盒模型 box-sizing: border-box
一个块的总宽度 = width(已包含padding和border) + margin(左右)


 

定位 position 

static 默认值,没有定位,元素出现在正常的文档流中;

relative 相对定位,相对位置为 自身默认位置;

absolute绝对定位,相对位置为 最近父非static的元素,没有就是body/可见视口/浏览器窗口;

fixed固定定位,对象位置为 body/可见视口/浏览神龙教主器窗口;

sticky粘性定位,该定位基于用户滚动的位置;

static 默认

relative、absolute 淘宝京东轮播图左右箭头、分页器、热卖商品、新品等等

fixed 传统网站两侧滚动广告、还有右下角客服、返回顶部等等

sticky 网站导航栏 例如 https://www.discuz.net/
 

水平垂直居中 🐝

方法1:【移动推荐】利用flex弹性布局 justify-content/ align-items center

方法2:【需要宽高】利用position定位 top/left 50% margin-top/left 负自身一半

方法3:【需要宽高】利用position定位 top/right/bottom/left =0 然后 margin: auto

方法4:【留心兼容】利用position定位 transform translate(-50%,-50%)

方法5:【留心兼容】利用position定位 calc((100% - 100px) / 2); 

等等

两栏布局 🐝

方法1:左固定浮动、右触发BFC

方法2:左固定浮动、右margin-left

方法3:左固定定位、右margin-left

方法4:父弹性盒、左固定、右flex:1 

等等

<style>
*{padding:0px;margin:0px;}
.test {width:50px;height:50px;background: black;color:#fff;}
</style>

<!-- 方法1:左固定浮动、右触发BFC -->
<style>
.left1 {width:200px;height:100px;background:#ccc;float:left;}
.right1 {height:100px;background:red;overflow: hidden;}
</style>
<div class="left1">你好</div>
<div class="right1"><div class="test">中国</div></div>

<!-- 方法2:左固定浮动、右margin-left -->
<style>
.left2 {width:200px;height:100px;background:#ccc;float:left;}
.right2 {height:100px;background:red;margin-left:200px;}
</style>
<div class="left2">你好</div>
<div class="right2"><div class="test">中国</div></div>

<!-- 方法3:左固定定位、右margin-left -->
<style>
.left3 {width:200px;height:100px;background:#ccc;position:absolute;}
.right3 {height:100px;background:red;margin-left:200px;}
</style>
<div class="left3">你好</div>
<div class="right3"><div class="test">中国</div></div>

<!-- 方法4:父弹性盒、左固定、右flex:1  -->
<style>
.main {display: flex;}
.left4 {width:200px;height:100px;background:#ccc;}
.right4 {height:100px;background:red;flex:1;}
</style>
<div class="main">
<div class="left4">你好</div>
<div class="right4"><div class="test">中国</div></div>
</div>

弹性布局 flex布局 🐝

https://www.zhangxinxu.com/wordpress/2018/10/display-flex-css3-css/#flex-direction

容器属性

子项属性

flex-direction: row | row-reverse | column | column-reverse;

order

flex-wrap: nowrap | wrap | wrap-reverse;

flex-grow

flex-flow: <‘flex-direction’> || <‘flex-wrap’>

flex-shrink

justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly;

flex-basis

align-items: stretch | flex-start | flex-end | center | baseline;

flex: none | auto | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]

align-content: stretch | flex-start | flex-end | center | space-between | space-around | space-evenly;

align-self

弹性和布局后,可以给子项上添加

order 子项排序、 默认0 子项原位置

flex-grow

flex-grow属性定义子项放大比例或分配容器的剩余空间,默认为0,即如果存在剩余空间,也不放大。

写1剩余空白间隙大家等分

写2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

flex-shrink 子项收缩规则、默认1 当空间不足时都将等比例缩小,写0则不缩小

flex-basis 子项分配剩余空间之前神龙教主默认大小、默认值是auto,就是自动子项本来大小。有设置width则占据空间就是width,没有设置就按内容宽度来

flex属性是flex-growflex-shrinkflex-basis的缩写。

  • flex默认值等同于flex:0 1 auto
  • flex:none等同于flex:0 0 auto
  • flex:auto等同于flex:1 1 auto
  • flex:1等同于flex:1 1 0

谈谈你对flex:1的理解 🐝

它是flex-growflex-shrinkflex-basis的缩写。剩余空间大家等分,空间不足等比缩小,子项分配剩余空间之前用本身大小

网格布局 Grid 布局

网格布局是将页面中的元素划分成一个个各自,然后控制小格子进行合并

https://www.zhangxinxu.com/wordpress/2018/11/display-grid-css-css3/

<style>
  .box {
    width: 300px;
    height: 400px;
    border: 10px solid gray;
    margin: 100px auto;

    display: grid;
    grid-template-rows: repeat(4, 100px);
    grid-template-columns: repeat(3, 100px);
  }

  .box > div {
    border: 2px dashed black;
    background-color: red;
  }

  .box1 {
    /* grid-row-start: 1;
    grid-row-end: 3;

    grid-column-start: 2;
    grid-column-end: 4;
     */
    grid-row: 1/3;
    grid-column: 2/4;
  }
</style>

<div class="box">
  <div class="box1">1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
  <!-- <div>10</div>
  <div>11</div>
  <div>12</div> -->
</div>

网格和弹性盒布局区别

  1. 维度:
    • 弹性盒布局是一维布局模神龙教主型,适合用于沿着一个方向(水平或垂直)排列元素。
    • 网格布局是二维布局模型,可以同时控制行和列,更适合复杂的布局结构。
  1. 控制:
    • 弹性盒布局通过设置容器的属性来控制子元素的排列方式、对齐方式、间距等。
    • 网格布局通过定义网格的行列数量、大小、对齐方式等属性来实现布局。
  1. 灵活性:
    • 弹性盒布局更适合于动态内容或不规则布局,可以根据内容的大小和数量自动调整布局。
    • 网格布局更适合于网格状的布局,可以精确控制每个网格的大小和位置。
  1. 兼容性:
    • 弹性盒布局的兼容性较好,适用于大多数现代浏览器。
    • 网格布局的兼容性也在不断改善,但相对于弹性盒布局,在一些老版本的浏览器上可能存在兼容性问题。

CSS三角形怎么实现

宽度、高度设为0,然后设置边框样式全透明

width: 0;
height: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;

■ 鸿蒙基础 ArkUI 

版本

推荐23年10-12月左右!!!!


接触鸿蒙多久了,什么时候开始的,项目用的api几 

用过 api 几个阶段


  • 回答:api9、api11、api12都用过
  • 分析
2023年  09月     api10
2023年  10月     api6/10都有 (6代码类似于小程序开发) 
2024年  02月     api11  (不对外开放合作伙伴用)                                   DevEco4
2024年  03月后   陆续用api11  (对外开放)                                        DevEco4
2024年  05月     用api12  (不对外开放合作伙伴用 他们是华为深度合作的外包公司  优先使用) DevEco5.0.0
2024年  06月19   陆续用api12 (对外开放)
2025年  02月     陆续用api14                                                    DevEco5.0.2

内容溢出如何解决 🐝

.maxLines(2) 
  .textOverflow({ overflow: TextOverflow.Ellipsis }) // 超长文本裁剪略号代替
// .textOverflow({ overflow: TextOverflow.Clip }) //   超长文本裁剪
// .textOverflow({ overflow: TextOverflow.MARQUEE }) // 超长文本滚动显示

特殊的场景:展开收起评论 需要计算、文字跑马灯

Text里面是否可以写子组件

可以写Span子组件

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/arkts-common-components-text-display-V13#%E6%B7%BB%E5%8A%A0%E5%AD%90%E7%BB%84%E4%BB%B6

如何复用样式

通过装饰器 @Styles、@Extend

@Styles、@Extend有什么区别

@Extend更强 支持font-size、font-weigth等等、并且可以传递参数

有几种布局方式 🐝

  • 鸿蒙中常用的布局方式 
  • ArkTs你说一下有几种布局方式?
线性布局 (Row/Column)
层叠布局 (Stack)
网格布局 (Grid)
弹性布局 (Flex)
相对布局 (RelativeContainer)
栅格布局 (GridRow/GridCol)

轮播图问题

  • 用过轮播图吗,有没有遇到什么问题,比如卡顿的情况
  • 使用轮播组件的时候有没有遇到过其他问题吗?怎么解决的
单独写一个Swiper卡顿

解决:加一个if判断,有数据时渲染

网格布局,层叠布局的应用场景

网格布局:场景有九宫格图片展示、日历、计算器等

层叠布局:当一个元素覆盖在另一个元素上时  例如返回顶部、弹窗广告等

绝对定位和相对定位

  • 用过相对布局吗?说一下相对布局
  • 绝对定位和相对定位的区别? 这俩个谁是以父容器定位的
.position({x:0,y:0})  绝对定位,确定子组件相对父组件的位置。
.offset({x:0,y:0}) 相对偏移,组件相对原本的布局位置进行偏移。

在鸿蒙里面官方推荐使用什么布局

页面创建时,扁平化减少了中间的嵌套层级,使总的组件节点的数量越少,在进行布局时所需要进行的计算相对越少。
页面更新时,当要更新的结构是嵌套子树的结构,其树内包含过多节点时,整体更新会导致更新的节点数过多,造成布局性能劣化。
所以当页面不存在冗余节点时,可以考虑是否能够通过替换为更高级的布局使得页面扁平化,来达到减少节点数的目的。主要方式可以参考:

RelativeContainer 通过相对布局实现扁平化。
绝对定位 通过锚点定位实现扁平化。
Grid 通过二维布局实现扁平化。


或者回答

- 使用Column/Row代替Flex
- 减少层级嵌套,通过相对布局 RelativeContainer

总元素节点为20个Text的情况下,来对比其性能消耗。通过Profiler工具获取其首帧绘制时间进行对比。对比结果如下表:

对比指标

Column/Row

Stack

Flex

RelativeContanier

Grid/GridItem

首帧绘制

7.13ms

7.34ms

11.71ms

9.13ms

12.62ms

Measure

2.63ms

2.70ms

7.59ms

3.59ms

8.68ms

Layout

0.74ms

0.77ms

0.83ms

0.77ms

0.92ms



 

■ 鸿蒙进阶 ArkTS 

💕 week1 变量/判断/循环/函数等

let、const

面试题1:let和const区别

相同点-都是块级作用域/不能重复定义
不同点-let修改/const不能修改

面试题2:const真的不能修改吗

原始类型:不可以
对象类型:可以

原理:const不可以修改栈里面的地址,但是可以修改堆里面的数据

面试题3:暂时性死区

let前面的区域就是暂时性死区,必须先定义再使用

面试题4:let和const如何选

普遍用const、当需要再改成let 例如Date、xhr、promise对象等

const d = new Date()

面试题5:var和let区别

var 函数作用域   可以重复定义 可以修改
let 块级作用域  不能重复定义 可以修改

说出JS数据类型有哪些 🐝

原始类型/基本类型:null、undefined、boolean、number、string、symbol、bigint

对象类型/引用类型/复杂/复合类型:

标准普通对象:object
标准特殊对象:Array、Date、Math、RegExp、Error……
非标准特殊对象:Number、String、Boolean……
可调用/执行对象「函数」:function

JS引用类型object、array还有哪些

可调用/执行对象「函数」:function
标准特殊对象:Array、Date、Math、RegExp、Error……
非标准特殊对象:Number、String、Boolean……

如何把数据强制转换为数值型

说明:面试概率低、工作概率高

语法:parseInt、parseFloat、Number

说出数据转换为布尔型的结果

null、undefined、0、NaN、空字符串结果是false、其他都是true

说出数据转换为字符串型的结果

都字符串

String(内容)、内容.toString()

说出数据转换为数值型的结果

Number(数据)

数据

结果

null

0

undefined

NaN

true/false

1/0

123/NaN

123/NaN

''/'123'/'123a'

0/123/NaN

null 和 undefined 有什么区别? 🐝

undefined 表示未定义,新定义的变量没有赋值就是undefined

null表示清空,当一个变量不用的时候,除了等待网页关闭销毁,也可以把它赋值为null。此时游览器会进行一个回收也就是清空内存。

了解1

typeof(null)会是一个object。最初这么设计的原因为:通常用作一个空引用一个空对象的预期,就像一个占位符。typeof的这种行为已经被确认为一个错误,虽然提出了修正,出于后兼容的目的,这一点已经保持不变。

了解2

null == undefined    true
null === undefined   false

为什么 0.1 + 0.2 !== 0.3? 你如何解决这个问题? 🐝❄️

因为 0.1 这样的数值用二进制表示你就会发现无法整除,

最后算下来会是0.0001100110011001...由于存储空神龙教主间有限,位数是无限的,只能取近似。

代码:0.1 + 0.2 == 0.3 // false

代码:0.625 + 0.625 == 1.25 // true

进制转换规则:https://www.runoob.com/w3cnote/decimal-decimals-are-converted-to-binary-fractions.html

十进制整数 -> 转二进制

举例:(5).toString(2) // 结果 101
转换:除2反向取余
2| 5    --- 余1
------
2| 2    --- 余0
------
1    

举例:(13).toString(2) // 结果 1101
转换:除2反向取余
2| 13    --- 余1
------
2| 6     --- 余0
------
2| 3     --- 余1
------
1    

核对:https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.pianshen.com%2Fimages%2F732%2Faf802c5be33b4934e6a6fec52a208734.png&refer=http%3A%2F%2Fwww.pianshen.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1642634303&t=293eccde3cc561a3ed2d38f8cf57eea9

十进制小数 -> 转二进制

举例:(0.625).toString(2) // 结果 0.101
转换:乘2正向取整
0.625*2=1.25======取出整数部分1 
0.25*2=0.5========取出整数部分0 
0.5*2=1==========取出整数部分1 

举例:(0.7).toString(2)  // 结果 0.101100110011001100110011 0011 ...
0.7*2=1.4========取出整数部分1 
0.4*2=0.8========取出整数部分0 
0.8*2=1.6========取出整数部分1 
0.6*2=1.2========取出整数部分1 
0.2*2=0.4========取出整数部分0  
0.4*2=0.8========取出整数部分0 
0.8*2=1.6========取出整数部分1 
0.6*2=1.2========取出整数部分1 
0.2*2=0.4========取出整数部分0
...


推荐解决: 浮点数转化成整数

实战使用:购物车结算时,商品价格(33.01)-优惠券价格(5),本应该是28.01,但是实际的结果是28.009999999999998

33.01 - 5 = 28.009999999999998

(33.01*100 - 5*100) / 100 = 28.01
  • es6 解决办法
es6 提供了 Number.EPSILON,这个值等于 2^-52 ,这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近 0,但不等于 0,这个时候我们只要判断误差值在这个范围内就可以说明是相等的。

function numbersequal(a,b){
 return Math.abs(a-b)<Number.EPSILON
}
var a = 0.1 + 0.2;
var b = 0.3;
console.log(numbersequal(a, b))
// true
  • 四舍五入

采用四舍五入方法,取了一个 10 位小数

function numTofixed(num) {
 if (typeof num == 'number') {
     num = parseFloat(num.toFixed(10))
 }
 return num;
}

numTofixed(0.1 + 0.2);
// 0.3
  • 使用第三方库
math.js
bignumber.js
....



 

💕 week2 对象/数组/内置对象/es6/正则

说出变量在内存中的分配 🐝

栈:存 原始类型【名字】&【数据】、对象类型的【名字】&【堆地址】

堆:对象类型数据

说出变量赋值内存分配 🐝

原始类型:变量赋值,栈开辟内存直接存数据 -> 数据互补影响对象类型:变量赋值,栈开辟内存,存放神龙教主堆地址 -> 数据相互影响

切记切记切记:上课讲的形参也会出现传值、传地址问题 

var num = 100      
function fn(n) {  
  // var 形参 = 实参
  // var n = num也就是100   原始类型 传值 不影响
  n = 200    
  console.log(n)   // 打印: 200
}
fn(num)             
console.log(num)   // 打印: 100

// --------------------------

var obj = { name: 'Jack' }    

function fn(o) {      
  // var 形参 = 实参
  // var o  = obj    对象类型赋值 传地址  相互影响
  o.name = 'Rose'     //    
  console.log(o.name) //  打印:Rose
}
fn(obj)         
console.log(obj.name) //  打印:Rose

栈                                            堆
obj:地址1                                     地址1:{ name: 'Jack改成Rose' }  
fn函数里面的o变量:地址1 函数调用完毕o变量就销毁了


 

@State装饰器响应式失效原因、如何解决 🐝❄️

鸿蒙规定一维数据变化有响应式,二维没有

解决:修改一维数据 或者 通过 @Observed、@ObjectLink装饰一下 放到子组件中用

或者

v2版@ObservedV2 @Trace 

1 把@component改成@ComponentV2    @State改成private或者@Local
2 给子类加@ObservedV2   @Trace

判断是否是数组 🐝

方法1:通过语法 Array.isArray() 

方法2:instanceof

方法3:原型链(constructor)

..... We 

Array.isArray([1, 2 ,3])       // true
[1, 2 ,3] instanceof Array     // true
[1, 2 ,3].constructor === Array // true

Array.isArray('aa')        // false
'aa' instanceof Array      // false
'aa'.constructor === Array // false


 

如何交换两个变量的值

  • 临时变量法
var a = 1
var b = 2;
var temp = a
a = b
b = temp
  • 加减法
var a = 1
var b = 2;
a = a+b  // a = 1+2 = 3
b = a-b  // b = 3-2 = 1
a = a-b  // a = 3-1 = 2
  • 解构赋值法 week3
var a = 1
var b = 2;

[a, b] = [b, a]
  • 数组法
var a = 1
var b = 2
a = [a, b]
b = a[0]
a = a[1]
  • 对象法
var a = 1
var b = 2
a = { a: b, b: a }
b = a.b
a = a.a
  • 等等太多了


 

说出数组有哪些方法 🐝

·数据操作:shift/unshift/pop/push

遍历数据: forEach/map/filter/find/findIndex 

工作常用:concat/join/indexOf/includes

学习常用:reverse/splice

了解:sort

说出数组哪些方法会改变原数据

数据操作:shift/unshift/pop/push

学习常用:reverse/splice

了解:sort 

如何实现数组去重  🐝

方法1:通过es6新增的Set数据结构、和展开运算符去重 [...new Set(重复数组)] week3

方法2:通过filter配合indexOf实神龙教主现数组去重

方法3:定义空数组,通过forEach遍历重复的数组,通过indexOf判断当前值是否在数组中,不在就push

方法4:定义空数组,通过forEach遍历重复的数组,通过includes判断当前值是否在数组中,不在就push

方法5:利用对象的属性去重

方法6:....

// 方法1:通过es6新增的Set数据结构结果     [...new Set(重复数组)]
var arr = [1,2,3,2,3]
console.log([...new Set(arr)])


// 方法2:通过filter配合indexOf实现数组去重
var arr = [1,2,3,2,3]
var newarr = arr.filter(function(item, i) {
    // i=0  item值是1  arr.indexOf(item)  返回0  真
    // i=1  item值是2  arr.indexOf(item)  返回1  真 
    // i=2  item值是3  arr.indexOf(item)  返回2  真
    // i=3  item值是2  arr.indexOf(item)  返回1  假
    // i=4  item值是3  arr.indexOf(item)  返回2  假
    return i === arr.indexOf(item);
}) 
console.log(newarr)


// 方法3:定义空数组,通过forEach遍历重复的数组,通过indexOf判断当前值是否在数组中,不在就push
var newarr = []
var arr = [1,2,3,2,3]
arr.forEach(function(item, i) {
    if (newarr.indexOf(item) === -1)
    {
        newarr.push(item)
    }
})
console.log(newarr)


// 方法4:定义空数组,通过forEach遍历重复的数组,通过includes判断当前值是否在数组中,不在就push
var newarr = []
var arr = [1,2,3,2,3]
arr.forEach(function(item, i) {
    if (!newarr.includes(item))
    {
        newarr.push(item)
    }
})
console.log(newarr)

// 方法5:利用对象的属性去重
var arr = [1,2,3,2,3]
var newarr = []
var obj = {}
arr.forEach(function(item) {
  if (!obj[item]) {
    obj[item] = item // 出现记录
    newarr.push(item)
  }
})


 

说出字符串常用方法

工作常用:数组、查找、替换、截取、大小、空格

split、replace、substr、toUpperCase/toLowerCase、trim、slice

学习:.length、lastIndexOf、repeat


 

正则

匹配手机号:/^1\d{10}$/.test(数据)

匹配邮箱:/^\w{2,20}@\w{2, 20}.(com|org|cn|edu)$/.test(数据)

匹配中文:/^[\u4e00-\u9fa5]+$/.test(数据)

去所有空格:str.replaceAll(' ', '') 或 str.replace(/\s/g, '')

购物车去非数字:str.replace(/\D/g, '')


 

es6新增语法

https://github.com/tc39/proposals/blob/main/finished-proposals.md

# 2015年 ES6

    修饰符:let、const
    解构赋值:let {键:变量名=默认值,...,键:变量名=默认值} = {键:值,....,键:值} !!!
    字符串相关扩展:模板字符串
    函数相关扩展:箭头函数!!!、函数形参默认值、rest参数 arguments
    对象相关扩展:对象简写!!!、链判断操作符 ?.  空判断操作符??
    数组相关扩展:find/findIndex/indexOf/includes
    新增原始类型:symbol、bigint  
    -----------
    //       重要      中等        中等       重要          重要
    其他新增:展开运算符、新增数据结构、循环forof、module模块化、class类等等
    
# 2016年 ES7:Array.prototype.includes
# 2017年 ES8:Async functions
# 2018年 ES9:Promise.prototype.finally
# 2019年 ES10
# 2020年 ES11:String.prototype.matchAll、import()、BigInt、Promise.allSettled、
# 2021年 ES12:String.prototype.replaceAll、Promise.any、
# 2022年 ES13:

💕 week3 面向对象/事件

事件有哪些

通用事件按照触发类型来分类,包括触屏事件、键鼠事件、焦点事件和拖拽事件。

  • 触屏事件:手指或手写笔在触屏上的单指或单笔操作。
  • 键鼠事件:包括外设鼠标或触控板的操作事件和外设键盘的按键事件。
    • 鼠标事件是指通过连接和使用外设鼠标/触控板操作时所响应的事件。
    • 按键事件是指通过连接和使用外设键盘操作时所响应的事件。
  • 焦点事件:通过以上方式控制组件焦点的能力和响应的事件。
  • 拖拽事件:由触屏事件和键鼠事件发起,包括手指/手写笔长按组件拖拽和鼠标拖拽。
  • 事件分发:描述触控类事件(不包括按键,焦点)响应链的命中收集过程。

手势事件由绑定手势方法和绑定的手势组成,绑定的手势可以分为单一手势和组合手势两种类型,根据手势的复杂程度进行区分。

  • 绑定手势方法:用于在组件上绑定单一手势或组合手势,并声明所绑定的手势的响应优先级。
  • 单一手势:手势的基本单元,是所有复杂手势的组成部分。
  • 组合手势:由多个单一手势组合而成,可以根据声明的类型将多个单一手势按照一定规则组合成组合手势,并进行使用。

💕 week4 服务器请求 callback、promise、http、async/await 

谈谈你对HTTP理解

面试:HTTP是超文本传输协议,规定了客户端和服务端如何通信,然后由两个部分组成分别是请求、响应

学习:就是一个规则,你必须按照这个规则才可以和后端交互拿数据
概念:超文本传输协议
作用:规定客户端和服务端通信技术
场景:网页、APP等
请求组成:请求行(地址/状态吗/请求方式)、请求头(ua、content-type、token、cookie)、请求体(接口参数)
响应组成:响应行(同上)、响应头(暂略)、响应体(接口数据调试错误)

HTTP周边:强制缓存、协商缓存

强制缓存:就是文件直接从本地缓存中获取,不需要发送请求。

响应头 Cache-Control : 86400

expires

协商缓存/对比缓存 

在响应头部 Response Headers 中,有两种资源标识:

  • Last-Modified 资源的最后修改时间,对应请求头为 If-Modified-Since ;
  • Etag 资源的唯一标识,所谓唯神龙教主一,可以想象成时人类的指纹,具有唯一性;但 Etag 的本质是一个字符串;对应请求头为 If-None-Match 。 

Last-Modified 和 Etag

  • 当响应头部 Response Headers 同时存在 Last-Modified 和 Etag 的值时,会优先使用 Etag ;
  • Last-Modified 只能精确到秒级;
  • 如果资源被重复生成,而内容不变,则 Etag 更精确。

https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fupload-images.jianshu.io%2Fupload_images%2F5727550-fd8811d7defb8956.png&refer=http%3A%2F%2Fupload-images.jianshu.io&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650391279&t=0419cce2bf932f3c0e83594c4d177742

请求行/响应行:method、url、status

请求头headers、cookie、content-type、ua、if-none-match、if-modified-since 
请求体:请求参数

响应头:content-type 告诉浏览器如何解析数 、etag、last-modified、 Cache-Control...
响应体:响应的数据

get和post有什么区别

安全角度:post相对比get安全    原因get会在地址栏 因为在地址栏就有访问历史记录  post请求体不会存在来留痕
数据角度:post相对传输的数据比get多      get会受到不同浏览器地址栏长度限制,post服务器配置


上传图片:2M 一般只能上传png、jpg  gif不允许

谈谈你对http、https的理解,有什么区别

http超文本通讯协议 80
https也是超文本通讯协议  相对http更加安全  443

谈谈你对Event Loop理解

遇到异步代码会交给其他线程处理,然后放到队列中,

事件循环主要是从队列中取出代码放到执行栈中交给js引擎线程处理

说出宏任务、微任务各有哪些

单词含义:I input 输入、 O output 输出 

用户角度IO操作:鼠标键盘-是计算机输入信息,显示器-是输出设备

电脑角度IO操作:CPU、内存与其他设备之间数据转移过程就是IO操作,例如数据从磁盘读到内存,或者内存写到磁盘

编程角度IO操作:进程读取数据操作

  • 宏任务:
整体代码(script)
定时器(setTimeout、setInterval)
I/O操作(DOM事件、AJAX异步请求)

setImmediate(node环境)
requestAnimationFrame(浏览器环境)
  • 微任务 
Promise.then catch finally
async/await(底层还是promise)
process.nextTick(node环境) 
MutationObserver(浏览器环境)

说出先执行宏任务还是微任务

1宏n微

谈谈你对promise的理解

概念:ES6异步编程解决方案

作用:常用于封装ajax异步请求

说一下promise原理

底层创建了Promise构造函数,并且给该构造函数绑定了then、catch、finally等原型方法,和reject、resolve、all、race等静态方法。

说一下promise几个状态

进行中、成功了、失败了

const p = new Promise((resolve, reject) => {
  // 发送异步请求
  // 默认触发的  所以是进行中状态
})

追问:为什么状态不可逆

回答:底层Promise构造函数中会判断当前是否是pending进行中状态,不是就会终止代码执行 所以不可逆

// 明确:底层Promise源码大概是这么写的
function Promise(callback) {

    this.PromiseState = 'pending'
    this.PromiseResult = undefined
    
    const resolve = data => {
        if (this.PromiseState != 'pending') return
        this.PromiseState = 'fulfilled'
        this.PromiseResult = data
    }
    const reject = error => {
        if (this.PromiseState != 'pending') return
        this.PromiseState = 'rejected'
        this.PromiseResult = error
    }

    try {
        // callback(参数1, 参数2)
        // callback(() => {}, () => {})
        callback(resolve, reject)
    } catch(error) {
        reject(error.toString())
    }
}

// 然后:你写
const p = new Promise((resolve, reject) => {
  resolve(数据1)
  reject(数据2)
})

追问:状态之间怎么转换?

回答:通过promise的then机制,来实现状态切换

const p = new Promise((resolve, reject) => {
  resolve(数据1)
})

p
.then(res => {
  return Promise.resolve('失败的')
})

Promise.all、Promise.allSettled区别

Promise.all( 数组里面是一个个Promise对象 ) 有一个失败就走失败

Promise.allSettled( 数组里面是一个个Promise对象 ) 没有失败

Promise.race、Promise.any区别

Promise.race( 数组里面是一个个Promise对象 ) 根据第一个最快返回的决定状态

Promise.any( 数组里面是一个个Promise对象 ) 有一个成功就是then 都失败 才是catch


谈谈你对同步异步的理解

会被加入到队列的代码称之为异步代码,例如 http、setTimeout/setInterval、Promise.then 等等,他们不按书写✍🏻顺序执行打印结果的代码

按照书写顺序执行打印的代码称之为同步代码

谈谈你对async,await的理解

通过async修饰function、await修饰promise,

底层将await后面的表达式会先执行一遍,再将await下一行代码加入到微任务中。


 

■ 鸿蒙核心 

🥥 实战常问 🐝🐝🐝🐝🐝🐝🐝

  • 装饰器
  • 组件通信
  • 生命周期
  • 状态管理
  • 数据持久化
  • 进程线程
  • Stage 模型

  • Web 服务

  • 组件导航
  • 服务卡片
  • 一次开发,多端部署
  • 分层架构

🍃 api9、api12区别

  • ArkTS类型更加严格
使用具体的类型而非any或unknown
不支持通过索引访问字段
对象字面量不能用于类型声明
对象不支持解构赋值
部分支持展开运算符  =>  数组支持/对象不支持
不支持for .. in
等等
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/typescript-to-arkts-migration-guide-V13#%E4%B8%8D%E6%94%AF%E6%8C%81%E5%AF%B9%E5%87%BD%E6%95%B0%E5%A3%B0%E6%98%8E%E5%B1%9E%E6%80%A7
  • @Prop api12可以设置默认值、并且可以支持对象。
  • API12的文档和示例可能会更加丰富和完善,有助于开发者更快地掌握新特性并开始开发。
  • 生态系统变化 -> API12发布时,鸿蒙的生态系统可能已经变得更加成熟,有更多的第三方应用和服务可供集成。
  • 其他小细节
uniapp   把小程序项目生成wgt格式,然后通过鸿蒙点击直接打开小程序
...

🍃 路由 

- 跳转模式、和区别

两种分别是pushUrl和replaceUrl

区别:low一点 一个保留当前页  一个不保留
逼格:pushUrl会向页面栈中追加新页面,replaceurl直接替换页面栈中的当前页

- 实例模式

有两种,分别是

Standard 多实例/标准实例
Single 单实例


如何选: 商品详情、搜索详情  页面栈中  仅仅想保留一份  就单实例  否则都是多实例





用过, 商品列表页跳转商品详情页并且呢需要保留页面栈,设置调转到主题页如果要保证每次只有一个主题切换页存在于页面栈中用单实例,搜索列表页跳转详情页如果该结果已经被查看过了不要再新建页面则使用单实例。

商品列表、商品详情
登录、会员中心
搜索列表、搜索详情   单实例
设置、主题         单实例       结论:取决于需求

- 谈谈你对页面栈的理解和特点

特点:1-可以更方便路由 前进后退,2-提升性能  例如返回直接访问   如果没有页面栈返回重新创建开销大

追问:push的c相对的页面栈,replaceurl的b页面是什么样子的,c在站点还是a在站点  =》push/replace页面栈发生变化
pushUrl  页面栈新增一个
reaplceUrl 直接替换页面栈

- 页面栈的最大容量,超过会怎么样

页面栈的最大容量为32个页面。

报错 =》 解决:如果超过这个限制,可以调用router.clear方法清空历史页面栈,释放内存空间。

- 路由传参

router.pushUrl({
  url: '',
  params: {  a:1,b:2 }
})

const params = router.getParams() as ParamsType
console.log('接口参数:', JSON.stringify(params))
console.log('接口参数:', params.a)


 

🍃 自定义构建函数

- @Builder传参响应式失效如何解决

可以不传直接组件状态

或者引用传参(就是传对象)

- 如何实现全局Builder

wrapBuilder函数把@Builder包括导出就行

- 如何侦听@State数据变化

@Watch


 


- @BuilderParam是什么作用,其中的this指向是什么

组件通信传递UI数据的
或者
父传子,传递声明式组件数据



子中的this   因为是在子里面调用   =>   如果要父中this    把父的函数改成箭头

- @Builder 是做什么的,和组件有什么区别

自定义构建函数,主要用来增加builder层代码可读性 把ui层代码做了轻量级封装  

区别在于:没有生命周期、没有状态   轻量级封装; 如果仅仅是页面的封装直接@Builder  如果封装好了有很多状态、需要异步请求咱们得进一步封装成自定义组件。

- Builder里自定义传递参数,有两个传递方式,是哪两种;追问:参数类型和参数声明必须一致吗?能传undefined、null吗?

普通数据传递、和引用传递 也就是写对象格式传递状态才会有响应式效果   

参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。

- 如何封装全局@Builder

通过wrapBuilder:封装全局@Builder
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-wrapbuilder-V5


 

🍃 装饰器 ⭐️

- 装饰器有哪些

- 装饰器区别

@State组件内状态、@Prop父子单向、@Link父子双向、
@Provide/@Consume后代组件双向同步、@Observed/@ObjectLink嵌套类对象属性变化

@StorageProp/@StorageLink

@Watch、@Builder、@BuilderParam


@Track
还有v2新增的状态管理:@ObservedV2/@Trace、@Local、@Param、@Once、@Event、@Provider/@Consumer、@Monitor、@Computed

- @Link和@Prop区别🐝🐝

1、@Prop单向,@Link 双向
2、@Prop可以设置默认值、@Link不可以


这个不说了:api9 @Prop不可以设置默认值,并且@Prop不支持对象;  api12中@Prop升级了可以设置默认值,并且支持对象

- @state和@prop的区别

@State是组件状态 主要用来收集组件内的数据 例如表单,      也可以代替@Prop收数据 100%不用  所以后台状态管理升级了
@Prop是组件父把数据传递子组件中使用。

- 嵌套类型用什么装饰器

@Observed/@ObjectLink嵌套类对象属性变化

- 如果内部对象的数据变换,会实时刷新吗?🐝🐝

普通数组/对象可以
二维数据修改没有响应式,需要通过装饰器@Observed/@ObjectLink

- @prop,@Link需要初始化吗

@Link一直不行
@Prop  api9不可以   api12中可以了

- @Provide装饰器和@Consume装饰器得理解

@Provide 是用来声明某个模块提供的服务,其他模块可以通过这个装饰器进行引用。
@Consume 是用来声明某个模块需要使用的服务,从而实现依赖注入。
这种机制使得模块之间的耦合度降低,提高了代码的可维护性和重用性。通过这样一种方式,开发者可以更方便地实现模块之间的协作,更加灵活地管理项目的结构。

- @State能修饰复杂的数据类型吗?或者说嵌套对象

可以

- @watch是用来干什么的 追问:监听到了会做什么操作呢

侦听数据变化的,减少视图层写事件 -> 看需求

- @Watch作用,第一次加载是否会调用

侦听状态变化,不会

- @State原理 

- @ObjectLink的事件原理有了解过吗? 

- 鸿蒙装饰器作用和原理 

大概就是通过属性装饰器函数进行封装,

内部通过defineProperty劫持数据变化

// 属性装饰器函数
function logProperty(target: any, propertyKey: string) {
  let value = target[propertyKey];

  // 属性 getter
  const getter = function () {
    console.log(`Getting value of property ${propertyKey}: ${value}`);
    return value;
  };

  // 属性 setter
  const setter = function (newValue: any) {
    console.log(`Setting value of property ${propertyKey} to: ${newValue}`);
    value = newValue;
  };

  // 重新定义属性
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true,
  });
}

// 示例类
class MyClass {
  @logProperty myProperty: string = 'Initial value';
}

// 使用示例
const instance = new MyClass();
console.log(instance.myProperty);  // 获取属性值
instance.myProperty = 'New value'; // 设置属性值
console.log(instance.myProperty);  // 再次获取属性值

🍃 组件状态共享/组件通信 ⭐️

- 组件件的通信有几种方式🐝

不加修饰符  
@Prop单向
@Link双向
@Observed/@ObjectLink嵌套类对象属性变化
@Provide/@Consume后代组件双向同步、

@StorageProp/@StorageLink全局状态存储也可以实现组件通信

路由传参params 
@BuilderParam 传递声明式渲染数据




Emitter进行线程间通信   => on劫持数据传递,emit发送

import { emitter } from '@kit.BasicServicesKit';

emitter.on({
  eventId: 通信唯一标识
}, (eventData: emitter.EventData): void => {
  console.log('收到推送的消息:', JSON.stringify(eventData))
});


emitter.emit({
  eventId: 通信唯一标识,
  priority: emitter.EventPriority.LOW
}, {
  data: {
    content: 'c',
    id: 1,
    isEmpty: false
  }
});

- 父向子传传有几种写法🐝

不加修饰符  或者写 @State都可以
@Prop单向
@Link双向
@Observed/@ObjectLink嵌套类对象属性变化
@BuilderParam 传递声明式渲染数据

状态管理v2  @Param

- 鸿蒙跨层级的组件通信

@Provide/@Consume后代组件双向同步
Emitter进行线程间通信   => on劫持数据传递,emit发送
@StorageProp/@StorageLink全局状态存储也可以实现组件通信神龙教主

🍃 生命周期 ⭐ ️

- 分别说出组件、页面、UIAbility生命周期和场景 🐝🐝

※ 页面
  页面渲染前   aboutToAppear     请求接口
              build
  页面渲染后    onPageShow       访问量+1、登陆成功返回登录页展示最新的头像          只要来这个重新看这个页面就触发
  页面隐藏     onPageHide
  页面销毁     aboutToDisappear  清空定时器  释放内存
  系统返回      onBackPress      返回true终止路由跳转、false-正常        按两下退出


※ 组件(自定义组件)
  aboutToAppear      获取路由参数/请求接口
  build
  onDidBuild         埋点(统计这个页面访问次数  很少很少)
  aboutToDisappear   清空非鸿蒙资源 


※ 应用/窗口/UIAbility
  onCreate                  打开一个新窗口传递参数可以在这里获取;服务卡片跳转过来传递的参数也可以获取
  onWindowStageCreate       注册首选项、规定首次加载的页面
  onForeground              切换前台
  onBackground              切换后台
  onWindowStageDestory
  onDestory

- 自定义组件的生命周期

aboutToAppear      获取路由参数/请求接口
build
onDidBuild          埋点
aboutToDisAppear   清空非鸿蒙资源

- aboutToAppear在什么时候执行

build之前
 


🍃 状态管理 ⭐️

状态管理有哪些🐝🐝

组件状态  @State、@Prop、@Link、@Provide/@Consum、@Observed/@ObjectLink
应用状态  LocalStorage、AppStorage、PersistentStorage、Environment
以及状态管理周边的@Watch、$$运算符、@Track
还有v2新增的状态管理:@ObservedV2/@Trace、@Local、@Param、@Once、@Require、@Event、@Provider/@Consumer、@Monitor、@Computed

面试官提问方式
- 状态管理,修饰器有哪些
- 大概谈一谈你的那些状态管理,都用到了那些
- 组件拥有的状态管理
- 鸿蒙中的全局状态管理有哪些方式

AppStorage和Localstorage区别、哪里用?

区别
- 场景:LocalStorage页面状态、AppStorage全局状态
- 功能:AppStorage配合PersistentStorage支持数据持久化

场景:在多个页面之间进行状态数据共享有哪些方法

v2新增了哪些装饰器,为什么升级,有什么用🐝🐝

@ObservedV2/@Trace 属性深度侦听   可以解决对象深层数据响应式失效问题、还有@ObjectLink父级UI不刷新问题
@Local 纯组件内部状态、更加语义化    @State也可以接收父组件传递的数据
@Param 父传子 v1太多了   
@Once 初始化同步一次
@Event 子调用父的方法改变数据  子改父 
@Provider/@Consumer 跨组件层级双向同步,更强了支持方法、支持初始化、支持别名等
@Monitor 状态变量修改侦听,更强可以一次性侦听多个,可以获取修改前后数据
@Computed 计算属性

🍃 数据持久化 ⭐️

数据持久化用过哪些? 🐝🐝

用户首选项(Preferences):通过用户保存应用的配置信息,用户的个性化设置(字体大小,是否开启夜间模式)、用户登录信息、手动选择的城市、下拉上次更新日期、首页统计数据缓存、搜索历史、个人中心数据统计、个人中心最近学习缓存等等
键值型数据接口(KV-Store):非关系数据库,一般用的少,可以用于同应用跨设备数据同步,数据库备份与恢复。
关系型数据库(RelationalStore): 关系型数据库,可用来存储离线数据,例如离线收藏、使用搜索记录、视频缓存到本地列表等大量数据库缓存、首页列表数据缓存、下载、收藏、最近缓存。

首选项如何实现持久化 🐝🐝

a.utils目录下创建PreferenceUtil.ets工具
	- this.dataPreferences = preferences.getPreferencesSync(context, {name: 'myStore'})
	- this.dataPreferences?.putSync(key, value)
	- this.dataPreferences?.flushSync() 
	- this.dataPreferences?.getSync()
	
b.entryability中初始化PreferenceUtil实例

c.import {preferencesUtil} from '../utils/PreferenceUtil'  
preferencesUtil.get()
preferencesUtil.put()
preferencesUtil.set()

首选项和PersistentStorage区别

1.PersistentStorage是同步写入磁盘;Preferences是全量加载进内存。
2.PersistentStorage的持久化变量最好是小于2kb的数据;Preferences存储的数据不超过一万条 累计不超过50M,Key键为string类型,要求非空且长度不超过80个字节,如果Value值为string类型,长度不超过8192个字节。
3.PersistentStorage只能在UI页面内使用。
4.PersistentStorage不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能。如果开发者需要存储大量的数据,建议使用数据库api。
5.PersistentStorage存储AppStorage属性UI状态,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同;Preferences一般为应用保存用户的个性化设置(字体大小,是否开启夜间模式)等

PersistentStorage进行数据持久化,和preferences用户首选项使用场景? 

PersistentStorage适合存小于2Kb的数据,  
preferences 适合存用户的个性化设置(字体大小,是否开启夜间模式)、用户登录信息等数据,但是不要超多一万条神龙教主 累计不超过50M。

非UI页面使用用户首选项时context如何获取

可以在UIAbility里通过AppStorageLocalStorage里存储context,然后在非ArkUI页面里使用。

this.context

getContext(this)

通过用户首选项实现数据持久化之后,如果App更新版本,之前首选项的数据是否会保留

持久化之后的数据是存在文件中的,App更新版本之后是存在的。

关系型数据库(RelationalStore)如何实现

a.utils目录下创建RdbUtil.ets工具
			1. loadRdbStore   relationalStore.getRdbStore、 store.executeSql(tableSql)、保存起来
			2. 调用insert/delete/query实现增删查功能


b.entryability中初始化RdbUtil实例

c.import {xxxTable} from '../utils/RdbUtil'  
xxxTable.get()
xxxTable.put()
xxxTable.set()

键值型数据接口(KV-Store)如何实现

a.utils目录下创建KVStoreUtil.ets工具
	import { distributedKVStore } from "@kit.ArkData";
    import { common } from "@kit.AbilityKit";

    export type KVValueType =  Uint8Array | string | number | boolean

    class KVStoreUtil {

      private kvStore: distributedKVStore.SingleKVStore | undefined = undefined;

      // 创建商店对象
      loadKVStore(context:common.UIAbilityContext) {}

      // 操作商店对象
      put(key:string, value: KVValueType) {   }
      get(key:string) { }
      delete(key:string)  {}
    }

    export const kvStoreUtil = new KVStoreUtil()
	
b.entryability中初始化KVStoreUtil实例

c.import {kvStoreUtil} from '../utils/KVStoreUtil'  
kvStoreUtil.get()
kvStoreUtil.put()
kvStoreUtil.set()

跨模块、跨进程时如何保证正常读取首选项中数据?

跨模块:

由于context不同,获取到的是不同的首选项实例,因此会导致在跨模块、多页面等场景下取不到数据。此时可以考虑通过单例类在EntryAbility中存一个全局的context,或者使用应用级context。

跨进程:

不同进程一般只有在同沙箱的场景才能访问到一个preference文件,多进程可以通过dataGroupId在多个进程间共用一个preference文件。

首选项存储如果多个任务同时写入、并发的情况能保证数据的准确性吗

首选项无法保证进程并发安全,会有文件损坏和数据丢失的风险,不支持在多进程场景下使用。

对于多线程操作首选项和数据库是不是线程安全的?还是每一个线程独立的

是线程安全的。

用户首选项是线程安全的吗

首选项是线程安全的,所以多线程访问可以保证数据一致性,但只支持同进程,不支持多进程。

不依赖接口 用手机存储 怎么存储

自己通过文件管理直接写数据然后获取出来

let filePath = pathDir + "/test.txt";
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
let str: string = "hello, world";
let writeLen = fs.writeSync(file.fd, str);
console.info("write data to file succeed and size is:" + writeLen);
fs.closeSync(file);


 


🍃 进程线程 ⭐️

- 谈谈你对进程线程的理解 🐝🐝

进程:

  • 进程是正在执行的程序的实例,它是系统资源分配的基本单位。
  • 进程是一个独立的程序运行环境
  • 是系统进行资源分配的基本单位

线程:

  • 线程是进程内部的执行单位
  • 是操作系统能够进行运算调度的最小单位。

- 鸿蒙线程之间内存共享吗?

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/multi-thread-concurrency-overview-V5

不共享

ArkTS采用消息通信的Actor并发模型 所以具有内存隔离特性

java是内存共享的并发模型

- 鸿蒙arkts是单线程还是多线程、为什么是单线程呢

- 鸿蒙 stage 是单线程还是多线程

ArkTS是单线程的,单线程可以避免锁机制

- 鸿蒙如何实现多线程 🐝🐝

taskpool、worker

- 进程/线程通信🐝🐝

通信场景

能力支持

同Ability通信

Emitter、EventHub、CommonEvent

跨Ability通信

Emitter、EventHub、CommonEvent

跨线程通信

Emitter、CommonEvent、Worker、Taskpool

跨进程通信

CommonEvent、IPC&RPC

dataShare跨应用通信:用于应用管理其自身数据,同时支持同个设备上不同应用间的数据共享。

// eventHub   线程内通信
// 侦听 getContext(this).eventHub.on('自定义监听频道', (data:string,数据n:类型...) => console.log(data) )
// 推送 getContext(this).eventHub.emit('自定义监听频道', 数据, 数据n)

// emitter    线程间通信
// 侦听 emitter.on(  {eventId:通信唯一标识}, (e:emitter.EventData) => console.log(data) )
// 推送 emitter.emit( {eventId:通信唯一标识}, {  data:{content:数据} } )

// 公共事件-自定义事件
// 侦听  createSubscriber、subscribe
// 推送  publish


- 在taskpool中,@Concurrent任务池子线程返回数据,execute执行任务获取数据  从而实现线程通信 
- 在taskpool中,@Concurrent任务池子线程通过taskpool.Task.sendData发送数据,宿主线程监听到的数据task.onReceiveData((result: string) => {})
- 在worker中,彼此通过postMessage发送、通过onmessage获取消息
- 在worker中,Worker同步调用宿主线程的接口
```
宿主线程  workerInstance.registerGlobalCallObject("picData", picData);
然后,在Worker中通过callGlobalCallObjectMethod接口就可以调用宿主线程中的setUp()方法了。
```

- taskpool和worker区别 🐝🐝

短时任务用TaskPool、长时任务用worker 

场景对比

TaskPool和Worker均支持多线程并发能力。由于TaskPool的工作线程会绑定系统的调度优先级,并且支持负载均衡(自动扩缩容),而Worker需要开发者自行创建,存在创建耗时以及不支持设置调度优先级,故在性能方面使用TaskPool会优于Worker,因此大多数场景推荐使用TaskPool。

TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟且非长时任务)会被系统自动回收;而Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期。

常见的一些开发场景及适用具体说明如下:

  • 运行时间超过3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时)的任务。例如后台进行1小时的预测算法训练等CPU密集型任务,需要使用Worker。场景示例可参考常驻任务开发指导
  • 有关联的一系列同步任务。例如在一些需要创建、使用句柄的场景中,句柄创建每次都是不同的,该句柄需永久保存,保证使用该句柄进行操作,需要使用Worker。场景示例可参考使用Worker处理关联的同步任务
  • 需要设置优先级的任务。例如图库直方图绘制场景,后台计算的直方图数据会用于前台界面的显示,影响用户体验,需要高优先级处理,需要使用TaskPool。
  • 需要频繁取消的任务。例如图库大图浏览场景,为提升体验,会同时缓存当前图片左右侧各2张图片,往一侧滑动跳到下一张图片时,要取消另一侧的一个缓存任务,需要使用TaskPool。
  • 大量或者调度点较分散的任务。例如大型应用的多个模块包含多个耗时任务,不方便使用Worker去做负载管理,推荐采用TaskPool。场景示例可参考批量数据写数据库场景

TaskPool和Worker的实现特点对比

实现

TaskPool

Worker

内存模型

线程间隔离,内存不共享。

线程间隔离,内存不共享。

参数传递机制

采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。

采用标准的结构化克隆算法(Structured Clone)进行序列化、反序列化,完成参数传递。支持ArrayBuffer转移和SharedArrayBuffer共享。

参数传递

直接传递,无需封装,默认进行transfer。

消息对象唯一参数,需要自己封装。

方法调用

直接将方法传入调用。

在Worker线程中进行消息解析并调用对应方法。

返回值

异步调用后默认返回。

主动发送消息,需在onmessage解析赋值。

生命周期

TaskPool自行管理生命周期,无需关心任务负载高低。

开发者自行管理Worker的数量及生命周期。

任务池个数上限

自动管理,无需配置。

同个进程下,最多支持同时开启64个Worker线程,实际数量由进程内存决定。

任务执行时长上限

3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时),长时任务无执行时长上限。

无限制。

设置任务的优先级

支持配置任务优先级。

不支持。

执行任务的取消

支持取消已经发起的任务。

不支持。

线程复用

支持。

不支持。

任务延时执行

支持。

不支持。

设置任务依赖关系

支持。

不支持。

串行队列

支持。

不支持。

任务组

支持。

不支持。

- Worker和TaskPool的线程数量是否有限制

TaskPool内部会动态调整线程个数,不支持设置数量,只需要往线程池中抛任务,确保高优先级任务的及时执行。

Worker的线程个数最多64个,如果Worker超过规定个数,会创建失败。

在使用时,TaskPool与Worker两者独立,不相互影响,因此Worker在达到上限数量时,不会影响TaskPool。Worker是固定数量,当前是64个。TaskPool线程池的数量会根据硬件条件、任务负载等情况动态调整。

- 鸿蒙有几个进程线程 

系统的进程模型如下图所示。

  • 通常情况下,应用中(同一Bundle名称)的所有UIAbility、ServiceExtensionAbility和DataShareExtensionAbility均是运行在同一个独立进程(主进程)中,如下图中绿色部分的“Main Process”。

UIAbility 页面能力 窗口 -> 加载页面

ServiceExtensionAbility 后台服务扩展能力,提供后台运行并对外提供相应能力。

DataShareExtensionAbility 数据共享扩展能力,用于对外提供数据读写服务。

  • 应用中(同一Bundle名称)的所有同一类型ExtensionAbility(除ServiceExtensionAbility和DataShareExtensionAbility外)均是运行在一个独立进程中,如下图中蓝色部分的“FormExtensionAbility Process”、“InputMethodExtensionAbility Process”、其他ExtensionAbility Process。

FormExtensionAbility:卡片扩展能力,提供卡片开发能力。

WorkSchedulerExtensionAbility:延时任务扩展能力,允许应用在系统闲时执行实时性不高的任务。

InputMethodExtensionAbility:输入法扩展能力,用于开发输入法应用。

AccessibilityExtensionAbility:无障碍服务扩展能力,支持访问与操作前台界面。

FileShareExtensionAbility:文件共享扩展能力,用于应用间的文件分享。预留能力,仅系统应用支持。

等等

  • WebView拥有独立的渲染进程,如下图中黄色部分的“Render Process”。

鸿蒙加载h5网页

图1 进程模型示意图

  • Stage模型下的线程主要有如下三类:
    • 主线程
      • 执行UI绘制。
      • 管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。
      • 管理其他线程的ArkTS引擎实例,例如使用TaskPool(任务池)创建任务或取消任务、启动和终止Worker线程。
      • 分发交互事件。
      • 处理应用代码的回调,包括事件处理和生命周期管理。
      • 接收TaskPool以及Worker线程发送的消息。
    • TaskPool Worker线程
      • 用于执行耗时操作,支持设置调度优先级、负载均衡等功能,推荐使用。
    • Worker线程
      • 用于执行耗时操作,支持线程间通信。

TaskPool与Worker的运作机制、通信手段和使用方法可以参考TaskPool和Worker的对比

- 主线程有哪些职责呢 

  • 主线程
    • 执行UI绘制。
    • 管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上。
    • 管理其他线程的ArkTS引擎实例,例如使用TaskPool(任务池)创建任务或取消任务、启动和终止Worker线程。
    • 分发交互事件。
    • 处理应用代码的回调,包括事件处理和生命周期管理。
    • 接收TaskPool以及Worker线程发送的消息。

- 如何实现异步开发/并发/处理 

Promise和async/await

- 谈谈你对pormise的理解,有几个状态 

异步编程解决方案

romise有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。Promise对象创建后处于pending状态,并在异步操作完成后转换为fulfilled或rejected状态。

- promise和async、await关系 

promise是异步编程解决方案

async/await是一种用于处理异步操作的Promise语法糖,使得编写异步代码变得更加简单和易读。通过使用async关键字声明一个函数为异步函数,并使用await关键字等待Promise的解析(完成或拒绝),以同步的方式编写异步操作的代码。 

一般做项目接口返回的数据都保存在promise中,咱们通过async/await就可以获取出来

- 异步处理和主线程有什么关系、如何通信

异步操作的设计初衷就是为了减少对主线程(UI线程)的影响,从而提升应用的响应性和用户体验。

通信直接修改@State状态就行

- 你的项目中有没有用到多线程

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/multithread-develop-case-V5

- promise 并发是单线程还是多线程

遇到pormise会放到微任务队列,然后等js线程执行完毕,再由event loop事件循环对象把promise数据交给js线程处理

- 子线程和主线程的优先级及任务执行策略是什么

主线程作为UI线程,拥有最高优先级。在负载较高时,执行会更快;负载较低时,效率差别不大。

子线程可以通过优先级设置,任务优先级等影响调度。

- 在多线程并发场景中,如何实现安全访问同一块共享内存

可以使用共享对象SharedArrayBuffer实现。SharedArrayBuffer对象存储的数据在同时被修改时,需要通过Atomics原子操作保证其同步性,即下个操作开始之前务必需要等到上个操作已经结束。

🍃 文件基础服务

谈谈你对沙箱的理解

应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录”。

  • 对于每个应用,系统会在内部存储空间映射出一个专属的“应用沙箱目录”,它是“应用文件目录”与一部分系统文件(应用运行必需的少量系统文件)所在的目录组成的集合。
  • 应用沙箱限制了应用可见的数据范围。在“应用沙箱目录”中,应用仅能看到自己的应用文件以及少量的系统文件(应用运行必需的少量系统文件)。因此,本应用的文件也不为其他应用可见,从而保护了应用文件的安全。
  • 应用可以在“应用文件目录”下保存和处理自己的应用文件;系统文件及其目录对于应用是只读的;而应用若需访问用户文件,则需要通过特定API同时经过用户的相应授权才能进行。

沙箱目录和缓存清除机制

  • cache 应用缓存目录(应用cache目录大小超过配额或者系统空间达到一定条件,自动触发清理该目录下文件;随应用卸载而清理)
  • preferences 用户首选项(随应用卸载而清理)
  • temp 应用临时文件路径(应用退出后即清理。保存应用的临时生成的数据,主要包括数据库缓存、图片缓存、临时日志文件、以及下载的应用安装包文件等。此路径下存储使用后即可删除的数据)
  • files 默认长期保存的文件路径;随应用卸载而清理。

图片上传


1、使用Picker选择媒体库的图片与视频 (返回一个临时的图片地址 file:// 咱们可以直接预览 也可以继续向后走 拿到服务器地址再预览)

const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
...

const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then().catch()

2、把图片拷贝到应用的缓存目录 (不支持直接通过相册本地路径请求接口,仅支持通过缓存目录上传 context.cacheDir)

- 2.1 通过 openSync 读取 媒体库文件
- 2.2 通过 fileIo.copyFileSync(file.fd媒体文件, copyFilePath沙箱文件)

细节:copyFilePath沙箱中文件生成规则必须唯一 

fileType
fileName   时间戳 + 随机数1000~9999 + . 后缀  (别忘了含后缀)
copyFilePath

3、上传文件 request.uploadFile到服务器

request.uploadFile(上下文信息, 匹配对象{
  请求方式method: 'POST'
  请求地址url: '',
  请求头
  header: {},
  请求参数
  files: [
      {  name: '参数名',  uri: "internal://cache/沙箱目录下的文件名含后缀",    type:'',filename:'文件名含后缀'   }
  ]
})
.then(uploadTask => {
    // uploadTask.on(类型, 回调函数)
    // progress 订阅上传任务进度事件,使用callback异步回调。
    // headerReceive 服务器返回的
    // complete 订阅上传任务完成
    // fail 订阅上传任务失败
})

拍照上传

// 原来:Picker选择媒体库资源
// 现在:拍照返回的也是媒体库资源路径file://...
let pickerProfile: picker.PickerProfile = {
	cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
let pickerResult: picker.PickerResult = await picker.pick(getContext(this),
[picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO], pickerProfile);
console.log("the pick pickerResult is:" + JSON.stringify(pickerResult));
this.uris.push(pickerResult.resultUri)


和图片上传一样,区别  camera.CameraPosition.CAMERA_POSITION_BACK

附件上传

import  { picker } from '@kit.CoreFileKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
import log from '@open/log';

@Entry
@Component
struct Test {
  build() {
    Button('选择文档上传').onClick(() => {
      // 一、获取临时的图片地址
      const documentSelectOptions = new picker.DocumentSelectOptions();
      // 创建文件选择器实例
      const documentViewPicker = new picker.DocumentViewPicker(getContext(this));
      documentViewPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
        //文件选择成功后,返回被选中文档的uri结果集。
        // uris = documentSelectResult;
        console.info('documentViewPicker.select to file succeed and uris are:' + documentSelectResult);
        // 二、拷贝到沙箱目录  cacheDir
        const context = getContext(this)
        const fileType = documentSelectResult[0].split('.').pop()  as string  // ✅ 小心
        const fileName = Date.now() + '_' + (Math.floor(Math.random() * (99999 - 11111)) + 11111) + '.' + fileType
        const copyFilePath = context.cacheDir + '/' + fileName
        const file = fileIo.openSync(documentSelectResult[0], fileIo.OpenMode.READ_ONLY)
        fileIo.copyFileSync(file.fd, copyFilePath)
        // 三、调用request上传
        let uploadTask: request.UploadTask;
        let uploadConfig: request.UploadConfig = {
          url: 'http://123.56.141.187:8001/upload/create', // 需要手动将 url 替换为真实服务器的 HTTP 协议地址
          header: { 'Accept': '*/*' },
          method: "POST",
          files: [
            { filename: fileName, name: "file", uri: "internal://cache/"+fileName, type: fileType }
          ],
          data: [{ name: "serverImageName", value: fileName }],
        };
        try {
          request.uploadFile(getContext(), uploadConfig).then((data: request.UploadTask) => {
            // -----
            data.on('progress', (uploadedSize: number, totalSize: number) => {
              console.log('上传中:',  totalSize, uploadedSize)
            })
            data.on('headerReceive', (data: object) => {
              console.log('订阅上传任务HTTP响应事件:'+JSON.stringify(data))
              log.init({ close:false })
              log.info((data as Record<string,object>).body)
            })
            data.on('complete', () => {
              console.log('上传完成 http://tmp00002.zhaodashen.cn/'+fileName)
            })
            // -----
          }).catch((err: BusinessError) => {
            console.error(`Failed to request the upload. Code: ${err.code}, message: ${err.message}`);
          });
        } catch (err) {
          console.error(`Failed to request the upload. err: ${JSON.stringify(err)}`);
        }
        // -----------------
      }).catch((err: BusinessError) => {
        console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
      })
    })
  }
}

文件下载

request.downloadFile(getContext(), {
      url,
      filePath:  this.filePath + fileName // 字符串变数组 [...,'node-v22.16.0.pkg'] 通过pop弹出最后数据  pop是一个函数 返回弹出的数据
    }).then(uploadTask => {
      uploadTask.on("progress", (receivedSize: number, totalSize: number) => {
        // receivedSize 当前下载字节
        // totalSize 总字节
        console.log('数据:', receivedSize, totalSize)
        this.currentSize = receivedSize
        this.totalSize = totalSize
      })
      uploadTask.on("complete", () => {
        console.log('下载完成')
      })
    })
  }


资源目录存媒体库

import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

async function savePhotoToGallery(context: common.UIAbilityContext) {
  let helper = photoAccessHelper.getPhotoAccessHelper(context);
  try {
    // onClick触发后5秒内通过createAsset接口创建图片文件,5秒后createAsset权限收回。
    let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
    // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制
    let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件
    context.resourceManager.getMediaContent($r('app.media.startIcon').id, 0)
      .then(async value => {
        let media = value.buffer;
        // 写到媒体库文件中
        await fileIo.write(file.fd, media);
        await fileIo.close(file.fd);
        promptAction.showToast({ message: '已保存至相册!' });
      });
  }
  catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`);
  }
}

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column({ space: 10 }) {
        // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件
        Image($r('app.media.startIcon'))
          .height(400)
          .width('100%')

        SaveButton()
          .padding({top: 12, bottom: 12, left: 24, right: 24})
          .onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {
            if (result === SaveButtonOnClickResult.SUCCESS) {
              const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
              // 免去权限申请和权限请求等环节,获得临时授权,保存对应图片
              savePhotoToGallery(context);
            } else {
              promptAction.showToast({ message: '设置权限失败!' })
            }
          })
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

沙箱文件存媒体库

http://tmp00002.zhaodashen.cn/jd.png

import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';

async function savePhotoToGallery(context: common.UIAbilityContext, url:string) {
  let helper = photoAccessHelper.getPhotoAccessHelper(context);
  try {
    // onClick触发后10秒内通过createAsset接口创建图片文件,10秒后createAsset权限收回。
    let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
    // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制
    let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    // 写到媒体库文件中
    const source = image.createImageSource(url) // 图片对象
    const packer = image.createImagePacker() // 创建打包对象
    const buffer = await packer.packing(source, {
      quality: 100,
      format: 'image/jpeg'
    })
    await fileIo.write(file.fd, buffer);
    await fileIo.close(file.fd);
    promptAction.showToast({ message: '已保存至相册!' });
  }
  catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`);
  }
}

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column({ space: 10 }) {
        // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件
        Image($r('app.media.startIcon'))
          .height(400)
          .width('100%')

        SaveButton()
          .padding({top: 12, bottom: 12, left: 24, right: 24})
          .onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {
            if (result === SaveButtonOnClickResult.SUCCESS) {
              const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
              // 免去权限申请和权限请求等环节,获得临时授权,保存对应图片
              // =======================
              try {
                // 需要手动将 url 替换为真实服务器的 HTTP 协议地址
                request.downloadFile(
                  getContext(),
                  {
                    url: 'http://tmp00002.zhaodashen.cn/jd.png',
                    filePath: context.filesDir + '/hello3.jpg'
                  }
                ).then((data: request.DownloadTask) => {
                  let downloadTask: request.DownloadTask = data;

                  downloadTask.on('progress', (receivedSize: number, totalSize: number) => {
                    console.log('下载中:', receivedSize, totalSize)
                  })

                  downloadTask.on('complete', () => {
                    console.log('下载完成') // 保存到媒体库中✅
                    savePhotoToGallery(context, context.filesDir + '/hello3.jpg');
                  })
                }).catch((err: BusinessError) => {
                  console.error(`Failed to request the download. Code: ${err.code}, message: ${err.message}`);
                })
              } catch (err) {
                console.error(`Failed to request the download. err: ${JSON.stringify(err)}`);
              }
              // =======================
            } else {
              promptAction.showToast({ message: '设置权限失败!' })
            }
          })
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}


 

🍃 程序访问控制

申请权限

a 在配置文件中声明权限

b 校验当前是否已经授权 checkAccessToken

c 动态向用户申请授权 requestPermissionsFromUser

d 处理授权结果 requestPermissionOnSetting 如果用户授权,则可以继续访问目标操作。 如果用户拒绝授权,则需要提示用户必须授权才能访问当前页面的功能,并引导用户到系统应用“设置”中打开相应的权限。 在common架构base模块下封装授权文件

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/request-user-authorization-V5#%E5%BC%80%E5%8F%91%E6%AD%A5%E9%AA%A4

地理位置

1-配置申请位置权限 同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。

2-检查全局位置开关是否打开 geoLocationManager.isLocationEnabled();

  • 2.1 没开 atManager.requestGlobalSwitch(getContext(this), abilityAccessCtrl.SwitchType.LOCATION) 拉起全局设置位置开关页
  • 2.2 开了 继续

3-获取当前位置 geoLocationManager.getCurrentLocation(request) 可以获取用户经度纬度

4-可以继续通过geoLocationManager.getAddressesFromLocation()地理编码转化为可读性较强地址

  • 获取设备的位置信息,需要有位置权限,位置权限申请
// 位置信息(权限组)
{
  "name": "ohos.permission.APPROXIMATELY_LOCATION",
  "reason": '$string:permission_reason_location',
  "usedScene": {}
},
{
  "name": "ohos.permission.LOCATION",
  "reason": '$string:permission_reason_location',
  "usedScene": {}
},

按照步骤申请权限 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/location-guidelines-V13

import AccessControl from '../utils/AccessControl'
import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Test {

  async getLocation() {
    try {
      // 2. 位置开关是否打开
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('slj 2 检查全局位置开发是否打开')
      if (!locationEnabled) {
        // 用于UIAbility/UIExtensionAbility拉起全局开关设置弹框
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(this), abilityAccessCtrl.SwitchType.LOCATION).then((data: Boolean) => {
          console.info('slj 2延伸 success/cancel 拉起设置定位弹窗:' + JSON.stringify(data));
          return data // boolean
        }).catch((err: BusinessError) => {
          console.error('slj 2延伸 error 拉起设置定位弹窗:' + JSON.stringify(err));
          return false
        });
        if (!state) return
      }
      // 3. 获取当前位置
      let request: geoLocationManager.SingleLocationRequest = {
        'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
        'locatingTimeoutMs': 10000
      }
      try {
        geoLocationManager.getCurrentLocation(request).then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
          console.log('current location: ' + JSON.stringify(result));
          // current location: {"latitude":40.11524270229282,"longitude":116.24535759507577,"altitude":0,"accuracy":12.354018,"speed":0,"timeStamp":1735883027842,"direction":0,"timeSinceBoot":2116903847068,"additionSize":1,"additions":["requestId:d3485aca-b24b-451a-8057-26fc7600731f"],"additionsMap":{},"altitudeAccuracy":0,"speedAccuracy":0,"directionAccuracy":0,"uncertaintyOfTimeSinceBoot":0,"sourceType":2}

          try {
            geoLocationManager.getAddressesFromLocation({
              maxItems: 1, //指定返回位置信息的最大个数,
              longitude: result.longitude,
              latitude: result.latitude,
              locale: 'zh',
            }, (err, data) => {
              if (err) {
                console.log('getAddressesFromLocation err: ' + JSON.stringify(err));
                return
              }
              console.log('getAddressesFromLocation data: ' + JSON.stringify(data));
              console.log('纬度:', data[0].latitude)
              console.log('经度:', data[0].longitude)
              console.log('地址:', data[0].placeName)
              console.log('表示国家信息。countryName:', data[0].countryName)
              console.log('表示国家以下的一级行政区,一般是省/州。administrativeArea:', data[0].administrativeArea)
              console.log('表示国家以下的二级行政区,一般是市。subAdministrativeArea:', data[0].subAdministrativeArea)
              console.log('表示子城市信息,一般是区/县。subLocality:', data[0].subLocality)
              console.log('表示路名信息。locality:', data[0].locality)
              console.log('表示路名信息。:', data[0].locality)
            });
          } catch (err) {
            console.error("errCode:" + JSON.stringify(err));
          }

          // ....

        })
          .catch((error:BusinessError) => { // 接收上报的错误码
            console.error('promise, getCurrentLocation: error=' + JSON.stringify(error));
          });
      } catch (err) {
        console.error("errCode:" + JSON.stringify(err));
      }
      // ==========
    } catch (err) {
      console.error("errCode:" + err.code + ", message:"  + err.message);
    }
  }

  async aboutToAppear() {
    // 1, 获取设备的位置信息,需要有位置权限,位置权限申请的方法和步骤见申请位置权限开发指导。
    const state = await AccessControl.checkPermissions(['ohos.permission.LOCATION', "ohos.permission.APPROXIMATELY_LOCATION"])
    console.log('slj 1 授权', state)
    if (state) {
      this.getLocation()
    }
  }
  build() {

  }
}

持续定位

持续定位。多用于导航、运动轨迹、出行等场景。

首先要实例化ContinuousLocationRequest对象,用于告知系统该向应用提供何种类型的位置服务,以及位置结果上报的频率。

  • 设置locationScenario:

建议locationScenario参数优先根据应用的使用场景进行设置,该参数枚举值定义参见UserActivityScenario,例如地图在导航时使用NAVIGATION参数,可以持续在室内和室外场景获取位置用于导航。

  • 设置interval:

表示上报位置信息的时间间隔,单位是秒,默认值为1秒。如果对位置上报时间间隔无特殊要求,可以不填写该字段。

以地图导航场景为例,调用方式如下:

import { geoLocationManager } from '@kit.LocationKit';
let request: geoLocationManager.ContinuousLocationRequest= {
   'interval': 1,
   'locationScenario': geoLocationManager.UserActivityScenario.NAVIGATION
}
let locationCallback = (location:geoLocationManager.Location):void => {
   console.log('locationCallback: data: ' + JSON.stringify(location));
};
try {
   geoLocationManager.on('locationChange', request, locationCallback);
} catch (err) {
   console.error("errCode:" + JSON.stringify(err));
}

如果不主动结束定位可能导致设备功耗高,耗电快;建议在不需要获取定位信息时及时结束定位。

geoLocationManager.off('locationChange', locationCallback);

  • 完整代码
import AccessControl from '../utils/AccessControl'
import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Test {

  async getLocation() {
    try {
      // 2. 位置开关是否打开
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('slj 2 检查全局位置开发是否打开')
      if (!locationEnabled) {
        // 用于UIAbility/UIExtensionAbility拉起全局开关设置弹框
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(this), abilityAccessCtrl.SwitchType.LOCATION).then((data: Boolean) => {
          console.info('slj 2延伸 success/cancel 拉起设置定位弹窗:' + JSON.stringify(data));
          return data // boolean
        }).catch((err: BusinessError) => {
          console.error('slj 2延伸 error 拉起设置定位弹窗:' + JSON.stringify(err));
          return false
        });
        if (!state) return
      }
      // 3. 获取当前位置
      let request: geoLocationManager.SingleLocationRequest = {
        'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
        'locatingTimeoutMs': 10000
      }
      try {

        // 3.1 首次获取位置信息
        geoLocationManager.getCurrentLocation(request).then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
          console.log('current location: ' + JSON.stringify(result));
          // current location: {"latitude":40.11524270229282,"longitude":116.24535759507577,"altitude":0,"accuracy":12.354018,"speed":0,"timeStamp":1735883027842,"direction":0,"timeSinceBoot":2116903847068,"additionSize":1,"additions":["requestId:d3485aca-b24b-451a-8057-26fc7600731f"],"additionsMap":{},"altitudeAccuracy":0,"speedAccuracy":0,"directionAccuracy":0,"uncertaintyOfTimeSinceBoot":0,"sourceType":2}

          try {
            geoLocationManager.getAddressesFromLocation({
              maxItems: 1, //指定返回位置信息的最大个数,
              longitude: result.longitude,
              latitude: result.latitude,
              locale: 'zh',
            }, (err, data) => {
              if (err) {
                console.log('getAddressesFromLocation err: ' + JSON.stringify(err));
                return
              }
              console.log('slj 3.1 【直接】获取经度纬度 getAddressesFromLocation data: ' + JSON.stringify(data));
              console.log('纬度:', data[0].latitude)
              console.log('经度:', data[0].longitude)
              console.log('地址:', data[0].placeName)
              console.log('表示国家信息。countryName:', data[0].countryName)
              console.log('表示国家以下的一级行政区,一般是省/州。administrativeArea:', data[0].administrativeArea)
              console.log('表示国家以下的二级行政区,一般是市。subAdministrativeArea:', data[0].subAdministrativeArea)
              console.log('表示子城市信息,一般是区/县。subLocality:', data[0].subLocality)
              console.log('表示路名信息。locality:', data[0].locality)
              console.log('表示路名信息。:', data[0].locality)
            });
          } catch (err) {
            console.error("errCode:" + JSON.stringify(err));
          }

          // ....

        })
          .catch((error:BusinessError) => { // 接收上报的错误码
            console.error('promise, getCurrentLocation: error=' + JSON.stringify(error));
          });

        // 3.2 持续获取位置信息(含首次)
        try {
          geoLocationManager.on('locationChange', {
            'interval': 1, // 1s
            'locationScenario': geoLocationManager.UserActivityScenario.SPORT // 适用于记录用户位置轨迹的场景,如运动类应用记录轨迹功能。
          }, (location:geoLocationManager.Location):void => {
            console.log('slj 3.2【持续】获取经度纬度locationCallback: data: ' + JSON.stringify(location));
          });
        } catch (err) {
          console.error("errCode:" + JSON.stringify(err));
        }
        // ======
      } catch (err) {
        console.error("errCode:" + JSON.stringify(err));
      }
      // ==========
    } catch (err) {
      console.error("errCode:" + err.code + ", message:"  + err.message);
    }
  }

  async aboutToAppear() {
    // 1, 获取设备的位置信息,需要有位置权限,位置权限申请的方法和步骤见申请位置权限开发指导。
    const state = await AccessControl.checkPermissions([
      'ohos.permission.LOCATION', "ohos.permission.APPROXIMATELY_LOCATION",
      // 'ohos.permission.LOCATION_IN_BACKGROUND', // ✅ 新增1
    ])
    console.log('slj 1 授权', state)
    if (state) {
      this.getLocation()
    }
  }
  build() {

  }
}

后台定位-长时任务

应用退至后台后,在后台需要长时间运行用户可感知的任务,如播放音乐、导航等。为防止应用进程被挂起,导致对应功能异常,可以申请长时任务,使应用在后台长时间运行。在长时任务中可以申请多种类型的任务,并对任务类型进行更新。应用退后台执行业务时,系统会做一致性校验,确保应用在执行相应的长时任务。同时,系统有与长时任务相关联的通知栏消息,用户删除通知栏消息时,系统会自动停止长时任务。

  1. 需要申请ohos.permission.KEEP_BACKGROUND_RUNNING权限,配置方式请参见声明权限
  2. 声明后台模式类型。

在module.json5配置文件中为需要使用长时任务的UIAbility声明相应的长时任务类型(配置文件中填写长时任务类型的配置项)。

"module": {
     "abilities": [
         {
              "name": "EntryAbility",
               // ...
              "backgroundModes": [
                // 长时任务类型的配置项
                "audioRecording"
                或者
                "location"   ✅
             ]
         }
     ],
     ...
 }
  1. 创建BackgroundTaskUtil.ets文件封装长短时任务
import { common, WantAgent, wantAgent } from "@kit.AbilityKit";
import { BusinessError } from "@kit.BasicServicesKit";
import { backgroundTaskManager } from "@kit.BackgroundTasksKit";

class BackgroundTaskUtil {

  startLongTask(typeList: Array<string>) {
    let wantAgentInfo: wantAgent.WantAgentInfo = {
      // 点击通知后,将要执行的动作列表
      // 添加需要被拉起应用的bundleName和abilityName
      wants: [
        {
          bundleName: (getContext(this) as common.UIAbilityContext).abilityInfo.bundleName,
          abilityName: (getContext(this) as common.UIAbilityContext).abilityInfo.name
        }
      ],
      // 指定点击通知栏消息后的动作是拉起ability
      actionType: wantAgent.OperationType.START_ABILITY,
      // 使用者自定义的一个私有值
      requestCode: 0,
      // 点击通知后,动作执行属性
      actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
    };

    try {
      // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
      wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
        try {
          // let list: Array<string> = ["audioRecording"];
          backgroundTaskManager.startBackgroundRunning(getContext(this), typeList, wantAgentObj).then((res: backgroundTaskManager.ContinuousTaskNotification) => {
            console.info("Operation startBackgroundRunning succeeded");
            // 此处执行具体的长时任务逻辑,如录音,录制等。
          }).catch((error: BusinessError) => {
            console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
          });
        } catch (error) {
          console.error(`Failed to Operation startBackgroundRunning. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
        }
      });
    } catch (error) {
      console.error(`Failed to Operation getWantAgent. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
    }
  }

  stopLongTask() {
    backgroundTaskManager.stopBackgroundRunning(getContext(this)).then(() => {
      console.info(`Succeeded in operationing stopBackgroundRunning.`);
    }).catch((err: BusinessError) => {
      console.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
    });
  }
}

export default new BackgroundTaskUtil()
  • 示例代码
import AccessControl from '../utils/AccessControl'
import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import BackgroundTaskUtil from '../utils/BackgroundTaskUtil'

@Entry
@Component
struct Test {

  locationCallback(location:geoLocationManager.Location) {
    console.log('slj 3.2【持续】获取经度纬度locationCallback: data: ' + JSON.stringify(location));
  }

  async getLocation() {
    try {
      // 2. 位置开关是否打开
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('slj 2 检查全局位置开发是否打开')
      if (!locationEnabled) {
        // 用于UIAbility/UIExtensionAbility拉起全局开关设置弹框
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(this), abilityAccessCtrl.SwitchType.LOCATION).then((data: Boolean) => {
          console.info('slj 2延伸 success/cancel 拉起设置定位弹窗:' + JSON.stringify(data));
          return data // boolean
        }).catch((err: BusinessError) => {
          console.error('slj 2延伸 error 拉起设置定位弹窗:' + JSON.stringify(err));
          return false
        });
        if (!state) return
      }
      // 3. 获取当前位置
      try {

        // ✅ !!!!!
        // ✅ !!!!!
        // ✅ !!!!!
        // ✅ !!!!!
        // ✅ !!!!!
        BackgroundTaskUtil.startLongTask(["location"])

        // 3.1 首次获取位置信息
        // geoLocationManager.getCurrentLocation(request).then((result) => {})

        // 3.2 持续获取位置信息(含首次)
        try {
          geoLocationManager.on('locationChange', {
            'interval': 1, // 1s
            'locationScenario': geoLocationManager.UserActivityScenario.SPORT // 适用于记录用户位置轨迹的场景,如运动类应用记录轨迹功能。
          }, this.locationCallback);
        } catch (err) {
          console.error("errCode:" + JSON.stringify(err));
        }
        // ======
      } catch (err) {
        console.error("errCode:" + JSON.stringify(err));
      }
      // ==========
    } catch (err) {
      console.error("errCode:" + err.code + ", message:"  + err.message);
    }
  }

  async aboutToAppear() {
    // 1, 获取设备的位置信息,需要有位置权限,位置权限申请的方法和步骤见申请位置权限开发指导。
    const state = await AccessControl.checkPermissions(['ohos.permission.LOCATION', "ohos.permission.APPROXIMATELY_LOCATION"])
    console.log('slj 1 授权', state)
    if (state) {
      this.getLocation()
    }
  }

  aboutToDisappear() {
    BackgroundTaskUtil.stopLongTask()
    geoLocationManager.off('locationChange', this.locationCallback);
  }

  build() {

  }
}


 

🍃Stage模型 UIAbility ⭐️

UIAbility的理解 

UIAbility组件是系统调度的基本单元,为应用提供绘制界面的窗口。一个应用可以包含一个或多个UIAbility组件。

UIAbility 有几种启动方式🐝🐝

UIAbility的启动模式是指UIAbility实例在启动时的不同呈现状态。针对不同的业务场景,系统提供了三种启动模式:

说明:standard是multiton的曾用名,效果与多实例模式一致。

UIAbility生命周期区别,使用场景🐝🐝

UIAbility的生命周期包括
onCreate                  通过startAbility打开获取首次参数   onNewWant获取后续参数
  onWindowStageCreate 
onForeground              
onBackground
  onWindowStageDestroy
onDestroy四个状态

多个 uiability 之间是如何通信的

  • startAbility传参 onCreate、onNewWant获取
  • AppStorage
  • emitter
Button('打开花小猪小程序').onClick((event: ClickEvent) => {
    const want: Want = {
      // Want参数信息
      deviceId: '', // deviceId为空表示本设备
      bundleName: 'com.example.ability',  
      abilityName: 'MiniProgramAbility',
      parameters: {
        id: 1111,
        other: 2
      }
    };
    this.context.startAbility(want);
})


获取

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // 需 求:把 窗口数据 <=> 页面
  // 方案1:AppStorage存储
  // 方案2:eventHub
  AppStorage.setOrCreate('goodsImg', want.parameters?.goodsImg)
  AppStorage.setOrCreate('goodsTitle', want.parameters?.goodsTitle)

  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}

onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  AppStorage.setOrCreate('goodsImg', want.parameters?.goodsImg)
  AppStorage.setOrCreate('goodsTitle', want.parameters?.goodsTitle)
}

FA模型、Stage模型区别 

FA模型Stage有什么区别:核心区别  Stage模型多个UIAbility运行在同一个独立进程;FA模型每个页面组件独占一个ArkTS引擎实例
1、FA有 PageAbility、ServiceAbility、DataAbility; 而Stage模型   UIAbility、ExtensionAbility
2、FA模型2类进程    主进程,渲染进程 ;  而Stage三类进程   主进程、ExtensionAbility进程   渲染进程
3、FA模型通过config.json配置    Stage模型通过app.json5、module.json5配置

项目

FA模型

Stage模型

应用组件

1. 组件分类

- PageAbility组件:包含UI,提供展示UI的能力。详细介绍请参见PageAbility组件概述

。- ServiceAbility组件:提供后台服务的能力,无UI。详细介绍请参见ServiceAbility组件概述

。- DataAbility组件:提供数据分享的能力,无UI。详细介绍请参见DataAbility组件概述

。2. 开发方式通过导出匿名对象、固定入口文件的方式指定应用组件。开发者无法进行派生,不利于扩展能力。

1. 组件分类

- UIAbility组件:包含UI,提供展示UI的能力,主要用于和用户交互。详细介绍请参见UIAbility组件概述

。- ExtensionAbility组件:提供特定场景(如卡片、输入法)的扩展能力,满足更多的使用场景。详细介绍请参见ExtensionAbility组件概述

。2. 开发方式采用面向对象的方式,将应用组件以类接口的形式开放给开发者,可以进行派生,利于扩展能力。

进程模型

有两类进程:1. 主进程2. 渲染进程详细介绍请参见进程模型

有三类进程:1. 主进程2. ExtensionAbility进程3. 渲染进程详细介绍请参见进程模型

线程模型

1. ArkTS引擎实例的创建一个进程可以运行多个应用组件实例,每个应用组件实例运行在一个单独的ArkTS引擎实例中。2. 线程模型每个ArkTS引擎实例都在一个单独线程(非主线程)上创建,主线程没有ArkTS引擎实例。3. 进程内对象共享:不支持。详细介绍请参见线程模型

1. ArkTS引擎实例的创建一个进程可以运行多个应用组件实例,所有应用组件实例共享一个ArkTS引擎实例。2. 线程模型ArkTS引擎实例在主线程上创建。3. 进程内对象共享:支持。详细介绍请参见线程模型

应用配置文件

使用config.json描述应用信息、HAP信息和应用组件信息。详细介绍请参见应用配置文件概述(FA模型)

使用app.json5描述应用信息,module.json5描述HAP信息、应用组件信息。详细介绍请参见应用配置文件概述(Stage模型)

应用通信/跨设备通信🐝🐝

拉起指定应用 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/app-linking-startup-V5

let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
        let link: string = "https://www.example.com/programs?action=showall";
        // 仅以App Linking的方式打开应用
        context.openLink(link, { appLinkingOnly: true })
          .then(() => {
            console.info('openlink success.');
          })
          .catch((error: BusinessError) => {
            console.error(`openlink failed. error:${JSON.stringify(error)}`);
          });

处理链接

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { url } from '@kit.ArkTS';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 从want中获取传入的链接信息。
    // 如传入的url为:https://www.example.com/programs?action=showall
    let uri = want?.uri 
    if (uri) {
      // 从链接中解析query参数,拿到参数后,开发者可根据自己的业务需求进行后续的处理。
      let urlObject = url.URL.parseURL(want?.uri);
      let action = urlObject.params.get('action')
      // 例如,当action为showall时,展示所有的节目。
      if (action === "showall"){
         //...
      }
      //...
    }
  }
}
  • 拉起指定类型的应用

module.json5

{
  ...
  ...
  "querySchemes": [
    "amapuri",
  ],
  "requestPermissions": [
      {
        "name": "ohos.permission.PRIVACY_WINDOW"
      },
      {
        "name": "ohos.permission.GET_NETWORK_INFO"
      },
      ....
      ....
  ]
  ....
  ....
  ....
}

code

this.array.push(new Item('拉起短信界面并指定联系人', getMmsWant()))
this.array.push(new Item('拉起浏览器并打开指定网页', getBrowsableWant()))
this.array.push(new Item('拉起应用市场对应的应用详情界面', getAppGalleryDetailWant()))
this.array.push(new Item('拉起设置应用HOME-WLAN界面', getWifiEntryWant()))
this.array.push(new Item('拉起HOME-系统和更新-输入法页面', getSetInputWant()))
this.array.push(new Item('拉起开启定位的设置页', getLocationManagerSettingsWant()))
this.array.push(new Item('拉起拨号界面并显示待拨出的号码', makeCall))


function getMmsWant() {
  let want: Want = {
    bundleName: 'com.ohos.mms',
    abilityName: 'com.ohos.mms.MainAbility',
    parameters: {
      contactObjects: JSON.stringify([{ "contactsName": 'ZhangSan', "telephone": "16888880000" }]),
      content: "短信内容测试321",
      pageFlag: 'conversation'
    }
  };
  return want
}

function getBrowsableWant() {
  let want: Want = {
    action: 'ohos.want.action.viewData',
    entities: ['entity.system.browsable'],
    uri: 'https://www.huawei.com'
  };
  return want
}

function getAppGalleryDetailWant() {
  let bundleName = "com.amap.hmapp"
  let want: Want = {
    action: 'ohos.want.action.appdetail',
    uri: 'store://appgallery.huawei.com/app/detail?id=' + bundleName, //  bundleName为需要打开应用详情的应用的包名
  };
  return want
}

function getWifiEntryWant() {
  let want: Want = {
    bundleName: 'com.huawei.hmos.settings',
    abilityName: 'com.huawei.hmos.settings.MainAbility',
    uri: 'wifi_entry'  // 根据”设置”应用配置的界面信息,选择不同的uri
  };
  return want
}


function getSetInputWant() {
  let want: Want = {
    bundleName: 'com.huawei.hmos.settings',
    abilityName: 'com.huawei.hmos.settings.MainAbility',
    uri: 'set_input'  // 根据”设置”应用配置的界面信息,选择不同的uri
  };
  return want
}


function getLocationManagerSettingsWant() {
  let want: Want = {
    bundleName: 'com.huawei.hmos.settings',
    abilityName: 'com.huawei.hmos.settings.MainAbility',
    uri: 'location_manager_settings'  // 根据”设置”应用配置的界面信息,选择不同的uri
  };
  return want
}

function makeCall() {
  call.makeCall("13800000000", (err: BusinessError) => {
    if (err) {
      console.error(`makeCall fail, err->${JSON.stringify(err)}`);
    } else {
      console.log(`makeCall success`);
    }
  });
}


function checkApp() {
  try {
    let link: string = "amapuri://"
    let appName: string = "高德地图"
    let data = bundleManager.canOpenLink(link);
    hilog.info(0x0000, 'testTag', 'canOpenLink successfully: %{public}s', JSON.stringify(data));
    if (data) {
      promptAction.showToast({
        message: `${appName} APP 已安装`,
        duration: 2000,
        bottom: '500lpx'
      });
    } else {
      promptAction.showToast({
        message: `${appName} APP 未安装`,
        duration: 2000,
        bottom: '500lpx'
      });
    }
  } catch (err) {
    if (err['code'] == 17700056) {
      /*{
      "module": {
        "querySchemes": [
          "amapuri",
        ],*/
      promptAction.showToast({
        message: '请在src/main/module.json5配置link,参考注释 querySchemes',
        duration: 2000,
        bottom: '500lpx'
      });
    } else {
      promptAction.showToast({
        message: '未知异常',
        duration: 2000,
        bottom: '500lpx'
      });
    }
    let message = (err as BusinessError).message;
    hilog.error(0x0000, 'testTag', 'canOpenLink failed: %{public}s', message);
  }
}

显示Want和隐式Want区别?

  • 显式Want:在启动目标应用组件时,调用方传入的want参数中指定了abilityName和bundleName,称为显式Want。

显式Want通常用于应用内组件启动。

const want = {
  deviceId: '', // deviceId为空表示本设备
  bundleName: 'com.example.myapplication',   指定应用
  abilityName: 'FuncAbility',                 指定窗口
}
this.context.startAbility(want);
  • 隐式Want:在启动目标应用组件时,调用方传入的want参数中未指定abilityName,称为隐式Want。
const want =  {
  action: 'ohos.want.action.search',
  entities: [ 'entity.system.browsable' ],
  uri: 'https://www.test.com:8080/query/student',
  type: 'text/plain',
};
this.context.startAbility(want);

显式Want在启动目标应用组件时,显式指定了能力名称(abilityName)和包名称(bundleName),通常用于应用内组件启动。隐式Want未指定这些信息,主要通过动作(action)、实体(entities)、URI和类型(type)等匹配目标组件,系统会根据这些属性在安装的应用中查找合适的组件


 

■ 鸿蒙WebView应用开发

JSX、脚手架、类组件

🐝🐝🐝vue和react区别、vue2和vue3区别

思考:如果你是面试官你问 v2和v3区别 vue和react区别 你想听到什么或者可以从几个维度去回答

回答:

  • 第一种:拿出语法区别挨个说 例如 v3相对v2不支持过滤器、混入;....
  • 第二种:框架层面思想上设计上、语法层面、底层原理

测试:v2、v3

框架层面思想设计上

- v2不贴近原生很多封装好了 理解起来抽象   不方便二阶段学完很好吸收;v3  定义变量就是模型数据视图可以显示,但是没有响应式  ref、reactive  再比如方法 不需要写个抽象的methods  直接写方法 
- 还有去掉了抽象的过滤器、混入   而是通过hooks  其实就是函数 
- composition api、 hooks
- ts
- webapck  改成  vite 
- vuex 改成 pinia   切记细品pinia发展史  先去掉mutations  后来直接里面写composition api
  等等

语法层面:双向绑定、侦听器watch/watchEffect、组件实例需要暴露属性才可以外部使用public/private、生命周期、样式:deep/:global、增加teleport、emits、Fragments、Composition API、sfc script setup等等

底层原理:Object.defineProperty、Proxy; diff算法区别

测试:vue、react区别 

组件化:Vue的组件化更加直接,因为组件包含了模板、逻辑和样式;而React的组件化更加灵活,它只关注逻辑,样式可以使用外部的CSS文件或库。

- 相同点 
react和vue都推崇组件化,通过将页面拆分成一个一个小的可复用单元来提高代码的复用率和开发效率。 在开发时react和vue有相同的套路,比如都有父子组件传参,都有数据状态管理,都有前端路由等。

- 不同点:.vue template、script、style      .js/jsx/tsx     返回function、class  
React推荐的做法是JSX + inline style, 也就是把 HTML 和 CSS 全都写进 JavaScript 中,即 all in js;
Vue 推荐的做法是 template 的单文件组件格式(简单易懂,从传统前端转过来易于理解),即 html,css,JS 写在同一个文件(vue也支持JSX写法)

模板语法:Vue使用基于HTML的模板语法,而React使用JSX语法,它将HTML嵌入到JavaScript代码中。

状态管理:Vue使用名为Vuex、PInia的状态管理库,而React则使用一个名为Redux的库、和基于Redux封装的Dva模块

生态系统:React的生态系统更加庞大,拥有更多的第三方库和工具。Vue生态系统也很强大,但是相对来说较小。大厂加持

学习曲线:由于Vue的模板语法和直观的API,学习Vue相对来说比较容易。而React的JSX语法和函数式编程风格,需要一定的JavaScript知识和函数式编程理念。

移动开发:weex开发app uniapp、 rn/react native 开发app

开发团队:尤雨溪团队、Facebook

数据响应:Vue 的数据响应采用的是双向绑定方式,可以直接修改数据和视图;React 的数据响应采用的是单向数据流方式,需要手动更新数据和视图。

最终:vue 官方化; react 社区化 

🐝 jsx的优点

1、JSX 更加灵活,JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化

2、Template 更多约束、类型安全的,在编译过程中就能发现错误

等等

JSX 是一种 JavaScript 语法扩展,是 React 中定义 UI 的一种方式。相对于传统的模板语言,JSX 具有以下优点:

  1. 更加直观

JSX 可以将 HTML 和 JavaScript 代码混合在一起编写,使得代码更加直观和易于理解。开发者可以在组件中轻松地编写 HTML 标记和逻辑代码,不需要在模板和代码之间来回切换。

  1. 更加灵活

JSX 可以在 JavaScript 中使用表达式和变量,可以根据逻辑动态生成组件和元素,使得开发更加灵活。JSX 可以使用 JavaScript 的所有特性,如循环、条件语句、函数调用等。

  1. 更加强大

JSX 可以使用自定义组件、事件处理器、样式、属性等功能,可以创建复杂的 UI 界面。使用 JSX 可以更好地组织代码,使得代码更加易于维护和扩展。

  1. 更加高效

JSX 可以在编译时进行优化,可以生成更加高效的 JavaScript 代码,提高应用程序的性能。JSX 编译器可以将 JSX 转换为常规的 JavaScript 函数调用,这些函数调用会创建 React 元素对象,从而提高了渲染效率。

总的来说,JSX 具有更加直观、灵活、强大和高效等优点,使得 React 开发更加便捷和高效。但是,对于一些开发者来说,可能需要一些时间来适应 JSX 的语法和编写方式。

🌛 babel原理

Babel 是一个 JavaScript 编译器,它可以将最新版的 ECMAScript 代码转换为向后兼容的代码,以便在旧版浏览器或其他 JavaScript 环境中运行。Babel 的核心功能是将 ECMAScript 代码转换为 JavaScript AST(抽象语法树),然后根据一组规则(称为插件)对 AST 进行变换,最后将变换后的 AST 转换为 JavaScript 代码。

下面是 Babel 的编译过程:

  1. 解析:将 ECMAScript 代码解析成抽象语法树(AST)。
  2. 转换:对 AST 进行转换,可以添加、修改、删除节点等操作,例如将 ES6 的箭头函数转换为 ES5 的函数表达式。
  3. 生成:根据变换后的 AST 生成新的 ECMAScript 代码。

Babel 的插件系统是 Babel 的核心,它允许开发者根据需要添加、删除和修改 AST 节点,以达到所需的转换效果。Babel 内置了很多插件,同时还有很多社区维护的插件供使用。

Babel 还有一个很重要的概念:预设(preset)。预设是一组插件的集合,可以直接用于对代码的编译。Babel 官方提供了很多预设,例如 babel-preset-env、babel-preset-react 等。预设通常是针对某种场景或应用开发的,可以减少开发者配置插件的时间和精力。

Babel 在编译时还有一些重要的概念,例如 polyfill、source map 等。polyfill 是在编译时自动添加到代码中的补丁,用于实现一些在当前 JavaScript 运行环境中不支持的特性。source map 是一种映射关系,它将编译后的代码映射回原始代码,方便开发者在调试时快速定位问题。

总之,Babel 的原理是将 ECMAScript 代码转换为抽象语法树(AST),然后根据一组规则对 AST 进行变换,最后将变换后的 AST 转换为 JavaScript 代码。Babel 的插件系统和预设是 Babel 的核心,它们可以帮助开发者实现所需的转换效果。


状态、属性、合成事件、含TypeScript 

🐝🐝 setState语法是同步的还是异步的,什么时候会合并操作

react16:react控制异步/合并、非react控制同步/不合并react18:都是异步、归类合并

🌛 setState底层原理 

在 React 类组件中,setState() 方法用于更新组件的状态。当调用 setState() 方法时,React 会将新的状态合并到当前的状态中,然后重新渲染组件。

setState() 方法的底层实现可以分为以下几个步骤:

  1. 将新的状态对象与当前状态对象合并 神龙教主

当调用 setState() 方法时,React 会将传入的新的状态对象与当前的状态对象进行浅合并。这意味着只有新状态对象中包含的属性会被更新,而当前状态对象中的其他属性将保持不变。

  1. 将合并后的状态对象存储到组件实例中

合并后的状态对象将被存储到组件实例的一个内部属性中。这个内部属性称为_pendingState,表示待处理的状态。

  1. 判断是否处于批量更新模式

在 React 中,可以通过 batchedUpdates() 方法将多个状态更新操作合并为一个批量更新操作,从而提高性能。当调用 setState() 方法时,React 会判断当前是否处于批量更新模式。

如果处于批量更新模式,React 会将组件实例添加到更新队列中,等到下一次批量更新时再进行更新。

如果不处于批量更新模式,React 会立即进行更新。

  1. 触发组件更新

当合并后的状态对象被存储到组件实例中后,React 会触发组件的更新流程。在更新流程中,React 会先调用组件的 shouldComponentUpdate() 方法,判断是否需要更新组件。

如果需要更新组件,React 会重新调用组件的 render() 方法,生成新的虚拟 DOM 对象。然后,React 会通过 Diff 算法比较新的虚拟 DOM 和旧的虚拟 DOM,计算出需要更新的部分,并生成更新操作。

最后,React 会将更新操作应用到真实的 DOM 上,完成组件的更新。

总的来说,setState() 方法的底层原理是将新的状态对象合并到当前状态对象中,存储到组件实例中,并触发组件的更新流程。通过这个流程,React 可以保证组件的状态和 UI 同步更新。

🐝🐝🐝 函数组件和类组件区别

函数组件:无状态也就是模型数据,无生命周期 hooks 类组件:有状态,有生命周期

🐝🐝🐝 函数组件如何状态管理

可以通过redux、或 hook是新技术

🐝🐝🐝 状态state、和属性props区别;如何选

语法角度

state可以更新
props因为单向数据流不可以更改

功能角度

state主要是组件内部状态管理,例如收集表单数据、dialog状态管理等等
props主要子组件传递属性,例如普通的button按钮、table表格、page分页等等都有大量使用

🐝 谈谈你对合成事件的理解、优势、原理、和原生事件区别

是什么:React模拟原生 DOM 事件开发的一套事件系统

//  原生事件
<button onclick="处理函数">+1</button>
或
document.qs().onclick = function() {}
或
document.qs().addEventListener('click', function(){})


// 合成事件:这个onClick就是
<button onClick={xxx}>+1</button>

啥好处

如果把所有的事件处理函数都绑定在DOM上,那么在页面响应的时候就会收到影响,导致页面很慢。react为了避免这类DOM事件的滥用,同时屏蔽底层之间不同浏览器事件的系统差异,实现了一个中间层- syntheticEvent合成事件

更好的兼容性和跨平台
避免频繁绑定和解绑事件,也方便事件统一管理
减少内存消耗,挂载到documnet,React17事件委托的变更 React 树的根 DOM 容器中
等等

啥原理:

React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件,React17事件委托的变更 React 树的根 DOM 容器中)

https://img-blog.csdnimg.cn/img_convert/945dd88ba1f47ecac1b9f27c15d8b312.png

为啥升级

const Search = function() 
{
  const handleClick = (e) => {
e.stopPropagation();
  }, 
  return <input onClick={this.handleClick} /> 

  document.addEventListener('click', function() {
里面通过表示判断是当前input点击才会触发
}, false);
}

document.addEventListener('click', function() {
console.log('propagation')
}, false);

和原生区别

1、事件名称命名方式不同

原生事件命名为纯小写(onclick, onblur),而 React 事件命名采用小驼峰式(camelCase),如 onClick 等
// 原生事件绑定方式
<button onclick="handleClick()">+1</button>
// React 合成事件绑定方式
const button = <button onClick={handleClick}>+1</button>

2、事件处理函数写法不同

原生事件中事件处理函数为字符串,在 React JSX 语法中,传入一个函数作为事件处理函数。
// 原生事件 事件处理函数写法
<button onclick="handleClick()">+1</button>
// React 合成事件 事件处理函数写法
const button = <button onClick={handleClick}>+1</button>
  1. 阻止默认行为方式不同
在原生事件中,可以通过返回 false 方式来阻止默认行为,但是在 React 中,需要显式使用 preventDefault() 方法来阻止
// 原生事件阻止默认行为方式 神龙教主
<a href="http://www.mobiletrain.org/"
onclick="console.log('阻止原生事件'); return false"
>
阻止原生事件
</a>

// React 事件阻止默认行为方式
const handleClick = e => {
e.preventDefault();
console.log('阻止原生事件~');
}
const clickElement = <a href="http://www.mobiletrain.org/" onClick={handleClick}>
阻止原生事件
</a>

样式、判断、循环、表单、ref、组件通信、跨域

🐝🐝🐝react中如何解决样式污染问题

方案1:Inline styling 行内样式

方案2:CSS Modules 默认(不可能的)

方案3:SASS/LESS Modules UMI推荐

方法4:Styled Components CRA推荐 实战相对少

🐝🐝🐝数组常用方法、数组去重至少写3种方案 

二阶段面试题一堆

🐝🐝🐝为什么循环的时候要写key

1-提升性能

2-避免出现不可控bug

在 React 中,当我们使用循环渲染列表时,需要给每个列表项添加一个唯一的 key 属性,这是因为 React 在进行渲染时会利用 key 属性来判断哪些元素是新增的、哪些元素是删除的,从而进行最小化的 DOM 操作,提高性能。

具体来说,当列表项的顺序发生变化时,如果没有指定 key 属性,React 可能会将一个旧的列表项误认为是新的列表项,从而导致 DOM 操作不必要的重复。此外,如果列表项的顺序不变,但是有一些项被删除或添加,如果没有指定 key 属性,React 可能会将一个旧的列表项误认为是新的列表项,从而导致 DOM 操作不必要的重复。

因此,为了保证 React 渲染列表时的正确性和性能,需要给每个列表项添加一个唯一的 key 属性,通常使用列表项的 ID 或索引作为 key 属性的值,从而让 React 能够准确地识别每个列表项,进行最小化的 DOM 操作。神龙教主

🐝🐝🐝谈谈你对受控组件、和非受控组件的理解

受控组件:通过react控制表单的输入输出

非受控组件:通过DOM操作表单数据

🐝🐝🐝如何实现组件通信

父传子:props

父传子:children

子传父:调用父的方法

兄弟:状态提升

组件实例:ref =》 属于ref转发、ref引用实现组件通信

children属性 =》也是父传子 超文本

context

状态管理工具:redux、redux-toolkit(RTK)、mobx等

等等

🐝🐝🐝项目跨域是如何解决的?

1、谷歌插件

2、关闭浏览器安全机制

3、服务器声明允许跨域

4、前端代理 也就是修改配置文件

在vue中

a 在根目录创建vue.config.js (不用安装

module.exports = {
    devServer: {
        proxy: {  //配置跨域
          '/api': {
            target: 'http://kg.zhaodashen.cn/v2/', 
            changOrigin: true,  //允许跨域
            pathRewrite: {
              '^/api': '' 
            }
          },
        }
    },
}

b 修改api baseURL /api前缀

在react中

a 安装模块

yarn add http-proxy-middleware@0.19.1 -D
或
cnpm i http-proxy-middleware@0.19.1 -D

b 在src目录创建src/setupProxy.js

const proxy = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    proxy("/api", {
      target: "http://kg.zhaodashen.cn/mt/admin/", // 目标服务器网址
      changeOrigin: true, // 是否允许跨域
      secure: false, // 关闭SSL证书验证HTTPS接口需要神龙教主
      pathRewrite: {
        // 重写路径请求
        "^/api": "",
      },
    })
  );

  // ...
};

c 修改api baseURL /api前缀


 

生命周期

🐝🐝🐝react生命周期有哪些&应用场景

单词                              触发机制          应用场景

挂载
constructor                      首次              初始化ref、state等属性
static getDerivedStateFromProps  首次&状态/属性     较少,监控props更新state
componentWillMount               首次              弃1
render                           首次&状态/属性     渲染页面
componentDidMount                首次              异步请求、操作DOM 如echarts、swiper

更新属性/状态

static getDerivedStateFromProps   首次&状态/属性    较少,监控props更新state
componentWillReceiveProps         仅属性           弃2
shouldComponentUpdate             属性/状态         子组件减少渲染次数  性能优化
componentWillUpdate               属性/状态         弃3
render                            首次&状态/属性     渲染页面
getSnapshotBeforeUpdate           属性/状态          DOM更新前收集DOM信息交给componentDidUpdate
componentDidUpdate                属性/状态         监控更新进一步操作DOM、聊天、echarts

卸载
componentWillUnmount              组件卸载      清除非react资源 例如登陆定时器

错误
componentDidCatch                 后代组件发错异常错误

问1:react生命周期有哪些

答1:挺多的挂载、更新、卸载等等,然后我常用的就是componentDidMount

问2:具体呢

答2

componentWillMount
render
componentDidMount

componentWillUpdate
render
componentDidUpdate

componentWillUnmount
...

🐝🐝🐝react16.3新增了哪些、弃用了哪些

新增

单词                              触发机制        场景
static getDerivedStateFromProps   首次&属性/状态    案例较少,主要监控props变化更新state用    
getSnapshotBeforeUpdate           属性/状态        案例更少,在更新DOM之前收集DOM信息

弃用 

componentWillMount              首次             弃1
componentWillReceiveProps       仅属性           弃2
componentWillUpdate             属性/状态         弃3

🐝🐝🐝react用来优化性能的是哪一个生命周期,说一下实战如何优化使用

shouldComponentUpdate

避免父组件更新状态或者属性后避免子组件不必要渲染

🐝🐝🐝父组件更新状态或者属性如何避免子组件不必要渲染

通过shouldComponentUpdate

或者直接继承PureComponent

🐝🐝🐝说出新增的生命周期各自应用场景

单词                              触发机制        场景
static getDerivedStateFromProps   首次&属性/状态    案例较少,主要监控props变化更新state用    
getSnapshotBeforeUpdate           属性/状态        案例更少,在更新DOM之前收集DOM信息

🐝🐝父子组件mount生命周期执行顺序 

父子嵌套生命周期(一般个1~2个概率)
  mount 首次刷新或者组件销毁后重新创建**
    f will mount
    f render
      s will mount
      s render
      s did mount
    f did mount
  update 
    f will update
    f render
      s will receive props
      s will update
      s render
      s did update
    f did update
    
生命周期形参了解:仅给你传递意识

函数组件、hooks、含TypeScript

🌛 谈谈你对hooks理解

React Hooks 是 React 16.8 版本中引入的一项新特性,它可以让我们在函数组件中使用 React 的状态和生命周期等功能,从而减少类组件的使用,使代码更加简洁和易于维护。神龙教主

具体来说,Hooks 提供了一些特殊的函数,如 useStateuseEffectuseContext 等,这些函数可以让我们在函数组件中使用状态、副作用和上下文等功能。使用 Hooks,我们可以:

  1. 状态管理:通过 useState 函数,可以在函数组件中管理状态,而不需要使用类组件的 this.stateuseState 接受一个初始状态值,返回一个数组,其中第一个元素是当前状态值,第二个元素是用于更新状态的函数。可以使用解构赋值来获取这两个值。
  2. 副作用处理:通过 useEffect 函数,可以在函数组件中处理副作用,如访问 API、更新 DOM 等。useEffect 接受两个参数,第一个参数是一个函数,用于执行副作用,第二个参数是一个数组,用于指定依赖项。当指定的依赖项发生变化时,useEffect 中的函数将被重新执行。
  3. 上下文传递:通过 useContext 函数,可以在函数组件中获取上下文信息,而不需要使用类组件的 static contextTypeuseContext 接受一个上下文对象,返回上下文中的值。

Hooks 还提供了一些其他函数,如 useReduceruseCallbackuseMemouseRef 等,这些函数可以让我们在函数组件中实现更复杂的逻辑和功能。

使用 Hooks 的优点是它能够使代码更加简洁和易于维护,避免了类组件中的一些繁琐的操作,如生命周期函数、this 绑定等。同时,Hooks 也能够提高代码的可读性和可测试性,使代码更加模块化和可复用。

不过,在使用 Hooks 时需要注意,它有一些规则和限制,如只能在函数组件的最顶层使用 Hooks,不能在循环和条件语句中使用 Hooks 等。另外,Hooks 也需要考虑到性能问题,避免过多地进行不必要的副作用和状态更新。

🌛 用hooks 意义

使用 Hooks 的主要意义在于它可以让我们在函数组件中使用 React 的状态、生命周期和其他功能,从而减少类组件的使用,使代码更加简洁和易于维护。

在之前的 React 版本中,我们只能通过类组件来管理状态和处理生命周期等功能。而使用类组件有一些缺点,如需要继承 React.Component、需要在构造函数中绑定 this、需要在生命周期函数中处理副作用等。这些操作使代码变得繁琐和难以理解,同时也容易出现错误。

而使用 Hooks,我们可以直接在函数组件中管理状态和处理副作用等功能,避免了类组件的一些繁琐操作。同时,Hooks 也能够提高代码的可读性和可测试性,使代码更加模块化和可复用。

除此之外,使用 Hooks 还有以下一些优点:

  1. Hooks 可以使代码更加简洁和易于维护。
  2. Hooks 可以提高代码的可读性和可测试性。
  3. Hooks 可以避免一些常见的错误,如忘记绑定 this、在副作用函数中忘记清理等。
  4. Hooks 可以使代码更加模块化和可复用,减少了代码的耦合度。

总之,使用 Hooks 可以使我们的代码更加简洁、易于维护、可读性和可测试性更好,同时也可以避免一些常见的错误,提高代码的质量和可靠性。

🐝🐝🐝 hook种类

  1. useState:用于在函数组件中添加状态管理功能。
  2. useEffect:用于在函数组件中处理副作用,如数据请求、事件监听等。
  3. useContext:用于在函数组件中访问上下文数据。
  4. useReducer:用于在函数组件中实现复杂的状态管理,类似于 Redux。
  5. useCallback:用于在函数组件中缓存回调函数,避免无用的重新渲染。
  6. useMemo:用于在函数组件中缓存计算结果,避免重复计算。
  7. useRef:用于在函数组件中访问 DOM 元素或保存其他数据。
  8. useLayoutEffect:类似于 useEffect,但会在 DOM 更新之前同步调用回调函数。
  9. useImperativeHandle:用于在父组件中访问子组件的实例方法。
  10. useDebugValue:用于在开发阶段输出调试信息。

这些 Hooks 种类可以使函数组件具有类似于类组件的功能,使得函数组件在处理状态、生命周期、副作用等方面更加灵活和强大。同时,Hooks 也可以帮助我们避免一些常见的错误和降低代码的耦合度,从而提高代码的可维护性和可读性。

除了常用的 Hooks 种类,还有一些其他的 Hooks 可以用于特定的场景:

  1. useReducerWithMiddleware:用于在 useReducer 中实现中间件,类似于 Redux。
  2. useEventCallback:用于在函数组件中缓存事件回调函数,避免因为闭包引用导致的性能问题。
  3. useInterval:用于在函数组件中实现定时器功能。
  4. useDebounce:用于在函数组件中实现防抖功能。
  5. useThrottle:用于在函数组件中实现节流功能。
  6. useAsync:用于在函数组件中实现异步请求的状态管理。
  7. useIntersectionObserver:用于在函数组件中观察元素的可见性状态。
  8. usePrevious:用于在函数组件中缓存上一次的状态值。神龙教主

这些 Hooks 可以帮助我们更加方便地处理特定的场景,提高代码的复用性和可读性,同时也可以使得函数组件具有更加强大和灵活的功能。除了以上列举的 Hooks,还有很多其他的自定义 Hooks 可以根据实际需求来编写,从而使得函数组件的编写更加方便和高效。

以下是一些较为高级的 React Hooks:

  1. useLayout:用于获取组件的布局信息,包括宽度、高度、位置等。
  2. useVirtualList:用于优化长列表的性能,只渲染可见区域的部分数据。
  3. useMedia:用于根据媒体查询动态调整组件的样式或行为。
  4. useDrag:用于实现拖拽功能,可以获取拖拽的状态、位置等信息。
  5. useDrop:用于实现拖放功能,可以获取拖放目标的状态、位置等信息。
  6. useTransition:用于实现平滑的动画效果,可以控制组件在进入、退出、更新时的动画效果。
  7. useImmer:用于在函数组件中实现不可变数据的更新,类似于 Immer 库。
  8. useSpeechRecognition:用于实现语音识别功能,可以获取用户的语音输入并进行相应的处理。
  9. useBattery:用于获取设备的电池信息,包括电量、充电状态等。神龙教主

这些高级的 React Hooks 可以帮助我们实现更加复杂和灵活的功能,同时也能够避免一些常见的错误和提高代码的可维护性和可读性。但是需要注意的是,使用这些高级 Hooks 需要具有一定的 React 和 JavaScript 基础知识,并且需要根据具体场景来选择和使用合适的 Hooks。

🐝🐝🐝 你用过哪些 hooks

🐝🐝🐝 自己封装过哪些hook

  1. useLocalStorage:将数据存储到本地存储中,包括设置、获取和删除数据。 神龙教主
import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const storedValue = window.localStorage.getItem(key);
    return storedValue ? JSON.parse(storedValue) : initialValue;
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  const removeValue = () => {
    window.localStorage.removeItem(key);
  };

  return [value, setValue, removeValue];
}

export default useLocalStorage;
  1. useDebounce:防抖动 Hook,用于在用户输入停止后延迟一段时间再执行函数。
import { useState, useEffect } from 'react';

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timeout = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timeout);
    };
  }, [value, delay]);

  return debouncedValue;
}

export default useDebounce;
  1. useFetch:用于获取数据的 Hook,包括加载中、错误和数据三种状态。
import { useState, useEffect } from 'react';

function useFetch(url) {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
        setLoading(false);
      } catch (error) {
        setError(error);
        setLoading(false);
      }
    }
    fetchData();
  }, [url]);

  return [data, loading, error];
}

export default useFetch;
  1. useForm:用于处理表单数据的 Hook,包括设置、获取和重置表单数据。
import { useState } from 'react';

function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setValues((prevValues) => ({ ...prevValues, [name]: value }));
  };

  const resetForm = () => {
    setValues(initialValues);
  };

  return [values, handleChange, resetForm];
}

export default useForm;

🐝🐝🐝 useMemo、useCallback 有什么区别

🐝 useRef 需要注意什么

🌛 useRef 和 createRef 有什么区别

等等等

鸿蒙开发者班级


✨家人们点个csdn账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个csdn账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个csdn账号关注,会持续发布大前端领域技术文章💕 🍃

    ^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐