0%

请问以下代码输出什么?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import {Component} from "react";

interface DemoComponentState {
val: number;
}

export class DemoComponent extends Component<{}, DemoComponentState> {
constructor(props: {}) {
super(props);
this.state = {
val: 0,
};
}

componentDidMount() {
this.setState({val: this.state.val + 1}, () => {
console.log(this.state.val);
});

this.setState({val: this.state.val + 1}, () => {
console.log(this.state.val);
});

setTimeout(() => {
this.setState({val: this.state.val + 1}, () => {
console.log(this.state.val);
});

this.setState({val: this.state.val + 1}, () => {
console.log(this.state.val);
});
}, 0);
}

render() {
return null;
}
}

答案:1 1 2 2。
解析:

概述

position是CSS中非常重要的一个属性,也是面试中常考的考点之一!比如昨天我的面试中面试官就问我,absolute和fixed有什么区别?可惜我没有答上来。

那么这个position到底代表什么呢?顾名思义,就是位置,position决定了浏览器如何确定一个元素的位置。来看一下官方的定义:

1
2
The position CSS property sets how an element is positioned in a document. 
The top, right, bottom, and left properties determine the final location of positioned elements.

我们翻译一下这段话:position属性设置了一个元素如何在文档中定位。top, right, bottom, left属性决定了定位元素的最终位置。也就是说:position决定了如何放置一个元素,toprightbottomleft决定了元素的最终位置。

position的取值有5种,分别是:staticrelativeabsolutefixedsticky。下面我们来详细了解一下这5种取值。

在此之前先了解一个术语 - 已定位元素(positioned element),这个概念在CSS中非常重要,尤其是在理解absolutefixed时。
什么是已定位元素呢?来看一下官方的定义:
A positioned element is an element whose computed position value is either relative, absolute, fixed, or sticky. (In other words, it’s anything except static.)

说白了,只要元素的position属性不是static,那么这个元素就是一个已定位元素

static - 默认定位

这个是默认值,可以这么说,平时开发的时候我们基本用不到这个值,因为它什么也不做,元素就是正常按照文档流一个一个排列就完了。而且由于是默认值,如果不设置position属性,那么元素的定位方式就是static的。

1
2
3
4
5
<body>
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
div {
width: 100px;
height: 100px;
}
.div1 {
background-color: red;
}
.div2 {
background-color: green;
}
.div3 {
background-color: blue;
}

static

注意在position: static模式下,top, right, bottom, left是不起作用的,因为这些属性只对已定位元素有效。

下面的top: 20px不起作用,因为#child元素的position属性是static

1
2
3
4
#child {
top: 20px; /* This doesn't work! need nonstatic position */
background-color: red;
}

relative - 相对定位

记得之前有一次面试,面试官突然问我,position: relative你知道吧,我说那必须知道啊!然后他说:那么你说说relative到底相对的是谁?我一下就懵了,之前根本没有思考过这个问题。其实答案很简单,相对的是元素自己本来的位置。也就是说,relative是相对于元素自己本来的位置进行定位的。- 注意不是相对于父元素,这是误解。

下图中绿色方块定位方式设置为relative,相对其原始位置向右偏移100像素,向下偏移20像素,原始位置保留,没有影响红色方块和蓝色方块。

1
2
3
4
5
<body>
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
div {
width: 100px;
height: 100px;
}
.div1 {
background-color: red;
}
.div2 {
position: relative;
top: 20px;
left: 100px;
background-color: green;
}
.div3 {
background-color: blue;
}

relative

设置为relative的元素不会脱离文档流,最终位置相对于元素自己本来的位置进行偏移。原始的位置会保留,不影响其他元素(子元素除外)布局。

注意:relative会创建一个stack context,详情请看这里.

absolute - 绝对定位

absolute是指绝对定位,它是相对于最近的非static定位的父元素进行定位的。如果没有非static定位的父元素,那么它是相对于<html>元素进行定位的。

标记为position: absolute的元素会脱离文档流,不占据空间,不影响其他元素的布局。下图中绿色方块定位方式设置为absolute,相对于其最近的非static定位的父元素(body)进行定位,向右偏移100像素,向下偏移20像素,原始位置不保留,影响蓝色方块的布局(蓝色方块补充了绿色方块原来的位置)

1
2
3
4
5
6
7
<body>
<div class="container">
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
div {
width: 100px;
height: 100px;
}
.div1 {
background-color: red;
}
.div2 {
position: absolute;
top: 20px;
left: 100px;
background-color: green;
}
.div3 {
background-color: blue;
}

absolute

fixed - 固定定位

fixedabsolute非常相似,元素也会脱离文档流,不同的是fixed是相对于viewport进行定位的。也就是说,无论你怎么滚动页面,fixed元素都会固定在浏览器视口的某个位置。(仿佛固定在屏幕上一样)

fixed的使用场景有很多,比如常见的页面右下角的返回顶部按钮,基本都是用fixed实现的。比如下面这个例子,注意这种场景不能使用absolute来实现,否则当页面滚动时,元素会跟着滚动。

1
2
3
4
5
#child {
position: fixed;
bottom: 32px;
right: 32px;
}

sticky - 黏性定位

sticky - 黏性的;黏的,其实从这个名字就可以看出来,这种布局可以实现黏性效果,比如常见的吸顶效果(当页面滚动时,某个元素滚动到顶部时就固定不动了),下面我们看看如何实现sticky效果。

1
2
3
<div class="container">
<div class="content">This is a sticky bar</div>
</div>
1
2
3
4
5
6
7
8
9
div.container {
height: 500vh; // 500个视口高度,使页面可以垂直滚动
}
div.content {
position: sticky; // 设置为sticky
top: 16px; // 当滚动至距离父元素顶部16px时,元素固定,也就是变得sticky.
height: 64px;
background-color: #ad1414;
}

sticky元素不会脱离文档流,它会按正常的文档流进行布局,但是当页面滚动至临界距离时(上面设置的top: 16px),元素就会固定住,不会继续滚动了,所以sticky也是有参照元素的,它参照的元素是它最近的可滚动的祖先元素。

sticky是相对定位和固定定位的结合体。它的行为就像是relativefixed的合体,当元素在屏幕中可见时,它的行为就像relative,当元素在屏幕外时,它的行为就像fixed

position属性如何影响top, right, bottom, left的表现?

position属性的值会直接影响top, right, bottom, left的表现,具体如下:

总结

是否脱离文档流 是否保留原来位置 是否影响 top/bottom/left/right 参照元素 是否创建堆叠上下文
static 无(按照正常文档流布局)
relative 元素自身原本的位置 否(除非设置了 z-index 或其他触发条件)
absolute 最近的已定位祖先元素(非 static),否则为 <html> 否(除非设置了 z-index 或其他触发条件)
fixed 浏览器视口(viewport) 否(除非设置了 z-index 或其他触发条件)
sticky 部分(滚动时脱离) 元素原本的位置与视口之间的相对关系 否(除非设置了 z-index 或其他触发条件)

References

  1. https://developer.mozilla.org/en-US/docs/Web/CSS/position

什么是盒模型?

当浏览器对一个页面进行布局的时候,浏览器的渲染引擎会把每个元素看视为一个盒子(box),这个盒子就是盒模型。盒模型是CSS布局的基础,理解盒模型对于理解CSS布局非常重要。

box-model

盒模型的四个组成部分

内容区域(content)

以上图为例,中间的黑色矩形就是内容区域,内容区域是盒模型的核心部分,包含了元素的实际内容,比如文本、图片等。

内边距(padding)

上图中黑色矩形和蓝色矩形框之间的白色部分就是padding,内边距是内容区域和边框之间的空间。内边距可以用来增加内容区域和边框之间的距离。

边框(border)

上图中蓝色矩形框就是边框,边框是元素的边界,边框也是有宽度和颜色的。

外边距(margin)

上图中蓝色矩形框和最外层的黑色矩形框之间的透明色部分就是外边距,外边距是元素和其他元素之间的空间。外边距可以用来增加元素之间的距离。

box-sizing对于盒模型的影响

CSS中的box-sizing属性决定了如何计算一个元素的宽度和高度,它有两个取值:content-boxborder-box

content-box

content-box是默认值,表示元素的宽度和高度只包括内容区域,不包括内边距(padding)和边框(border)。也就是说,如果你设置了一个元素的宽度为100px,高度为100px,那么它的实际宽度和高度就是100px,不包括内边距和边框。

计算公式如下:

1
2
元素的实际宽度 = CSS中的width
元素的实际高度 = CSS中的height

看一段CSS代码,因为没有指定box-sizing属性,所以默认是content-box,所以box元素的宽度是100px,高度是100px。

1
2
3
4
5
6
.box {
width: 100px;
height: 100px;
padding: 10px;
border: 5px solid black;
}

border-box

border-box表示元素的宽度和高度包括内容区域、内边距和边框。也就是说,如果你设置了一个元素的宽度为100px,高度为100px,那么它的实际宽度和高度就是100px - padding - border。计算公式如下:

1
2
元素的实际宽度 = CSS中的width - padding-left - padding-right - border-left - border-right
元素的实际高度 = CSS中的height - padding-top - padding-bottom - border-top - border-bottom

看一段CSS代码,你知道如何计算元素的实际宽高吗?

1
2
3
4
5
6
7
.box {
box-sizing: border-box;
width: 100px;
height: 100px;
padding: 10px;
border: 5px solid black;
}

根据border-box的计算公式:

  • box元素的实际宽度 = 100px - 10px * 2 - 5px * 2 = 70px
  • box元素的实际高度 = 100px - 10px * 2 - 5px * 2 = 70px

Reference

  1. https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_box_model/Introduction_to_the_CSS_box_model

JavaScript的垃圾收集机制主要是为了自动管理内存,使得开发者不需要手动去分配和释放内存。这种机制主要依赖于一些算法来追踪和标记不再使用的对象,然后回收它们所占用的内存空间。以下是简述的两个主要机制:

  1. 引用计数(Reference Counting)

    • 在引用计数机制中,每个对象都维护着一个记录它被引用次数的计数器。每当有新的引用指向该对象时,计数器加一;当某个引用失效或被移除时,计数器减一。一旦某个对象的引用计数降为0,就说明这个对象不再被使用,可以安全地将其从内存中删除。
    • 但是,引用计数有一个显著的问题是它无法处理循环引用的情况,即两个或多个对象互相引用,但实际上这些对象已经不可达(无法从根对象访问到)。这会导致这些对象永远不会被回收,从而造成内存泄漏。
  2. 标记-清除(Mark-and-Sweep)

    • 标记-清除是现代JavaScript引擎中最常用的垃圾收集算法。在这个机制下,垃圾收集器会定期从根对象(如全局变量、当前调用栈中的局部变量等)开始遍历所有可达的对象,并给这些对象做上标记。完成标记阶段后,任何没有被标记的对象就被认为是不可达的,因此可以从内存中清除。
    • 这种方法解决了引用计数中的循环引用问题,因为它不关心对象之间的相互引用,只关注对象是否能通过根对象访问到。

除了上述两种基本的垃圾收集机制外,现代JavaScript引擎还可能采用其他优化技术,比如分代收集(Generational GC),它基于大多数对象很快变得不可用的假设,将对象分为不同的“代”,并更频繁地检查较新的对象以提高效率。

总的来说,JavaScript的垃圾收集机制旨在让开发者不必担心内存管理的问题,尽管如此,了解这些机制有助于编写更高效的代码,避免潜在的内存泄漏问题。

简介

居中是CSS中永恒的话题,而垂直居中则是这个永恒话题中的重中之重,无论是日常工作,还是面试,你永远都绕不开垂直居中,今天咱们来详细讲解一下垂直居中。我们不会简单的罗列css样式,而是按不同分类来处理,这样更有利于理解。

inline或者inline-*元素的垂直居中(本质是文本垂直居中)

padding-top = padding-bottom

inline元素是指不会独占一行的元素,比如span, a, img等等。inline元素的垂直居中,可以通过设置padding-top和padding-bottom为相同的值来实现垂直居中。考虑如下html代码:

1
<span>This is a span</span>

我们先设置个背景色,方便查看span元素的高度,然后设置padding-toppadding-bottom为16px,这样span元素内的文本就垂直居中了。此方案也适用于多行文本。

1
2
3
background-color: green;
padding-top: 16px;
padding-bottom: 16px;

line-height = height

如果是对block element里面的文本垂直居中(比如div, p内的文本),那么可以尝试设置line-height等于height,这样也可以实现垂直居中。- 此方案不适用于多行文本。(多行文本时,文本会超出容器外,因为line-height本质上设置的是行与行之间的垂直距离)

1
<div class="content">This is a div</div>
1
2
3
background-color: green;
height: 60px;
line-height: 60px;

需要注意的是span属于inline元素,height对于inline元素是无效的。inline元素的宽度和高度由内容决定,所以heightwidth对于inline元素是无效的。但是line-height对于inline元素是有效的。

block elements的垂直居中

以如下代码为例,我们需要将子元素child垂直居中于父元素parent

1
2
3
<div class="parent">
<div class="child">Child</div>
</div>

此时,又分为两种情况,一种是我们知道子元素的高度,另一种是我们不知道子元素的高度。这两种情况有不同的处理方式。

知道元素的高度

  1. 设置父元素position: relative, 子元素position: absolute
  2. 设置子元素height: 100px, 这个是必须的,此条件就是元素高度已知。
  3. 设置子元素top: 50%, margin-top: -height/2(50px)
1
2
3
4
5
6
7
8
9
10
11
12
13
.parent {
position: relative;
height: 400px;
width: 100%;
background-color: red;
}
.child {
position: absolute;
height: 100px; /* You know the height of the element */
top: 50%;
margin-top: -50px; /* half of element height */
background-color: green;
}

不知道元素高度

大多数情况下,元素的高度是未知的,这时候可以使用使用transform: translateY(-50%);代替margin-top: -50px;

1
2
3
<div class="parent">
<div class="child">Child</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
.parent {
position: relative;
height: 400px;
background-color: red;
}
.child {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: green;
}

使用table-cell

如果你不在乎子元素会被拉伸并填满父元素的话,可以使用table-cell来实现垂直居中。

1
2
3
<div class="parent">
<div class="child">Child</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
.parent {
position: relative;
display: table; /* 父元素使用table布局 */
height: 400px;
background-color: red;
}
.child {
display: table-cell; /* 子元素使用table-cell布局 */
vertical-align: middle; /* 垂直居中 */
background-color: green;
}

使用flex布局

这是目前来讲最方便的方式了,使用flex布局可以轻松实现水平和垂直居中。首先将父元素设置为flex布局,然后设置flex-direction: column;将布局方式改为纵向排列(默认是横向排列),然后设置justify-content: center;即可实现垂直居中。

1
2
3
<div class="parent">
<div class="child">Child</div>
</div>
1
2
3
4
5
6
7
.parent {
display: flex;
flex-direction: column;
justify-content: center;
height: 400px;
background-color: red;
}

References

  1. https://css-tricks.com/centering-css-complete-guide/

React Fiber架构

React的渲染可以分为两个主要阶段:Reconciler(协调阶段),和Committer(提交阶段)。

Reconciler(协调阶段)

工作内容: 构建Fiber树,比较新旧虚拟DOM的不同之处,生成一个变更记录,即一系列需要对真实DOM进行的操作。此阶段的特点:异步,并发,可中断。如果执行过程中有更高优先级的任务来了,那么会中断当前Reconciler的工作,转而处理更重要的任务。

Committer阶段

工作内容:将Reconciler阶段生成的变更记录应用到真实的DOM上。此阶段是同步的,不可中断的。
工作阶段:

  1. Dom更新前 - useEffect在这个阶段执行,通过微任务队列异步执行(页面渲染后执行)
  2. Dom更新 - 执行真实DOM的更新
  3. Dom更新后 - useLayoutEffect在这个阶段执行,同步(页面渲染前执行)

注意页面的渲染和JS的执行是互斥的,只有JS代码执行完,页面才能渲染,这就是useLayoutEffect的作用,可以在页面渲染前执行一些操作,比如调整布局。

数据结构

Fiber架构中采用FiberNode和FiberTree来描述虚拟DOM树。FiberNode是一个双向链表,每个节点都有一个指向父节点的指针,一个指向子节点的指针,一个指向兄弟节点的指针。FiberTree是一个树形结构,由FiberNode组成。

React diff算法

树比较

只做同层级结点比较,如果结点不存在了,则直接删除。不会继续比较其子树。这避免了夸层级移动操作,对于跨层级移动操作,相当于删除再重建。同层级结点移动呢?可以处理。

组件比较

只做同类型的组件比较,比如div和div比较,p和p比较,只有组件的类型相同,才进入子树进行深层次比较。如果类型不一致,则删除重新创建。

元素比较

对于同层级的元素结点。

  1. 元素在新集合中,但是不在原来的集合中,属于全新的结点,对集合进行插入操作。
  2. 元素在原来的集合中,但是不在新的集合中,则删除该元素。
  3. 元素在新集合中,也在原来的集合中,且元素并未更新,只是位置发生了变化,则进行移动操作。

双缓冲策略:

  1. current树负责呈现当前页面,而所有的更新都由workInProgress树来承接,当变更完成需要渲染时,将workInProgress树变成current树。

setState是同步还是异步?

这个问题需要区分开来看,在React18之前,如果executionContext被赋值了,代表该任务已经进入React调度流程中,此时React会对该任务进行异步处理(批量处理),如果executionContext没有被赋值,代表该任务还没有进入React调度流程,此时React会对该任务进行同步处理。想setTimeout, setInterval等函数都是不会进入React调度流程的,所以是同步处理。而合成事件都会进入到React调度流程中,所以会被异步处理。在React18后,如果使用createRoot创建根节点,那么setState会变成同步的。但是如果还是使用传统的render方式,那么和React18之前的处理逻辑一样。

虚拟DOM

虚拟DOM是React的核心概念之一,它是一个轻量级的JavaScript对象,用来描述真实DOM的层次结构。React通过比较新旧虚拟DOM的差异,然后只更新需要更新的部分,从而提高页面的渲染性能。

虚拟DOM的量大作用:

  1. 跨平台,因为虚拟DOM只是一个数据结构,所以可以在不同平台上使用,比如浏览器,移动端App等。这是虚拟DOM的重要特性,很多人没有意识到。
  2. 高性能,通过比较新旧虚拟DOM的差异,只更新需要更新的部分,减少了对真实DOM的操作,提高了页面的渲染性能。

React中嵌套组件的渲染顺序,生命周期,销毁顺序。

渲染顺序

当React开始渲染过程时,它首先从上到下、从父到子地渲染组件树。这意味着父组件会先于其子组件进行渲染。一旦父组件完成它的渲染(包括执行相关的生命周期方法),React会递归地进入该父组件的子组件,并按照同样的方式渲染它们。

生命周期顺序

  • 首次挂载时:
    • 父组件:constructor -> static getDerivedStateFromProps -> render -> 子组件重复相同流程 -> 子组件componentDidMount -> 父组件componentDidMount
  • 更新时(由于props变化或state变化):
    • 父组件:static getDerivedStateFromProps -> shouldComponentUpdate -> render -> 子组件重复相同流程 -> 子组件componentDidUpdate -> 父组件componentDidUpdate

销毁顺序

当组件被卸载时,React会以相反的顺序卸载组件树中的组件。也就是说,React首先卸载最深层的子组件,然后逐步向上,直到根组件。

受控组件和非受控组件

受控组件是指组件的值是由React的state来控制的,比如下面的input,其中关键一句就是value={this.state.value} 这样就把input的value和组件的state关联起来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
this.setState({value: event.target.value});
}

handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

自定义hook

模拟componentDidMount

当我们为useEffect的依赖传递空数组时,它只会在组件初始化时执行一次,这就相当于componentDidMount。注意,这个只是粗略的模拟,因为useEffect是异步执行,且时机是在浏览器渲染完成后,而componentDidMount是在浏览器渲染前同步执行。

1
2
3
4
5
6
7
import {useEffect} from "react";

export const useMount = ((callBack) => {
useEffect(() => {
callBack();
}, []);
})

模拟componentDidUpdate

componentDidUpdate是在每次组件的state或props更新是调用的,但是组件mount的时候不调用,所以我们需要一个变量来标志组件是否是第一次加载,这里我们使用useRef来标志组件是否是第一次加载。因为useRef包裹的值在整个组件声明周期内引用不变(有点像静态变量),并且这个值改变后不会触发组件的重新渲染。

1
2
3
4
5
6
7
8
9
10
11
export const useUpdate = (callback, deps) => {
const isMounted = useRef(false); // 🚩 关键点

useEffect(() => {
if (isMounted.current) {
callback();
} else {
isMounted.current = true; // 挂载后切换标识
}
}, deps);
};

需要注意的是,如果在React18+严格模式下运行该函数,还是会执行一次,因为React18+的严格模式会执行两次。

1
2
3
4
5
6
7
createRoot(document.getElementById('root')).render(
<StrictMode> // 严格模式,去掉即可得到正确结果
<BrowserRouter>
<App/>
</BrowserRouter>
</StrictMode>,
)

模拟componentWillUnmount

这个就比较简单了,componentWillUnmount是在组件卸载时调用的,我们可以使用useEffect的返回值来模拟这个生命周期。

1
2
3
4
5
6
7
export const useUnmount = ((callback) => {
useEffect(() => {
return () => {
callback();
}
}, []);
})

  1. How to implement responsive layout with CSS?
  2. What’s flex layout in CSS?
  3. 简述CSS中盒模型是什么?
  4. grid布局极其优点?
  5. 手写一个圣杯布局。
  6. 如何实现垂直居中?除了flex布局还有其他方案吗?
  7. float不是完全脱离文档流,这点和position: absolute有区别的。
  8. CSS中有哪些实现透明的方式?
  9. CSS中选择器有哪些?优先级是怎样的?
  10. 如何实现一个三角形?
  11. 如何实现一个圆形?
  12. 如何实现一个梯形?

Injection Context - 顾名思义,注入上下文,说的通俗点,就是在Angular代码中,什么位置可以注入,比如我们最常用的constructor就属于一个Injection Context,因为你可以在constructor中注入服务。

Angular支持的Injection Context有如下几种:

  1. In class constructor
  2. In the initializer for fields of such classes.
  3. In the factory function specified for useFactory of a Provider or an @Injectable
  4. In the factory function specified for an InjectionToken.
  5. Within a stack frame that runs in an injection context. - 这是个啥?我咋看不懂捏?

In class constructor

constructor是我们最常用的注入位置,比如我们在组件中注入服务,就是在constructor中注入的。

新的写法, 使用inject函数

1
2
3
4
5
export class AppComponent {
constructor() {
private service: MyService = inject(MyService);
}
}

旧的写法

1
2
3
4
5
export class AppComponent {
constructor(private service: MyService) {
console.log(service);
}
}

In the initializer for fields of such classes

这个是啥意思呢?就是在类的字段初始化器中,也可以注入服务,比如下面的DataService.

1
2
3
4
5
6
export class AppComponent {
private service: MyService = inject(MyService);
constructor() {
console.log(this.service);
}
}

Stack frame in an injection context

有些函数被设计成可以运行在injection context中,比如我们常用的路由守卫(router guard), 之所以这样是为了能让我们在路由守卫中注入服务。比如下面的canActivateTeam函数,就是一个路由守卫。在这个函数里,我们可以注入PermissionsServiceUserToken。这样就可以判断用户是否有权限访问某个页面。

1
2
3
4
const canActivateTeam: CanActivateFn =
(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
return inject(PermissionsService).canActivate(inject(UserToken), route.params.id);
};

Run within an injection context

有时候我们需要讲一个函数运行在injection context中,但是当前上下文并不是injection context, 这时,我们可以使用runInInjectionContext函数来创建一个新的injection context, 然后在这个新的injection context中运行我们的函数。

比如Angular框架要求effect函数是必须运行在injection context中,所以我们通常在构造函数体中运行effect函数,如果我们想在ngOnInit函数中运行effect函数呢?因为ngOnInit函数并不是injection context, 这时我们就可以使用runInInjectionContext函数来运行effect函数。

注意:使用runInInjectionContext函数需要一个EnvironmentInjector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export class CountComponent implements OnInit {
count = signal(0);
doubleCount = computed(() => this.count() * 2);

private environmentInjector = inject(EnvironmentInjector);

constructor() {
// effect(() => {
// console.log('count:', this.count());
// console.log('doubleCount:', this.doubleCount());
// });
}

ngOnInit() {
runInInjectionContext(this.environmentInjector, () => {
effect(() => {
console.log('count:', this.count());
console.log('doubleCount:', this.doubleCount());
});
})
}
}

References

  1. https://angular.dev/guide/di/dependency-injection-context