Skip to content

2024-03-20 面试 React

作者:guo-zi-xin
更新于:2 个月前
字数统计:8.6k 字
阅读时长:28 分钟

关于Vue组件通信

常用的组件通信方式有以下几种

  • props

  • $emit

  • ref 和 defineExpose

  • provide 和 inject

  • v-model

  • pinia

  • mitt

  • props

父组件将数据传给子组件,子组件通过props属性接收父组件传递的数据
  • 子组件 Child.vue:
vue
<script setup>
/**
 * 使用 defineProps 函数来定义 Props 的类型和默认值
 * defineProps 不需要引入就可以直接使用
*/

const props = defineProps({
  // 变量 count 是通过父组件传递过来的
  count: {
    type: Number,
    default: 0
  }
})
</script>

上段代码中, 使用defineProps函数来定义props的类型和默认值。

  1. defineProps的参数中,我们可以定义一个对象,其中的每个属性代表一个Prop
  2. 在这个示例中, 我们定义了一个名为 count 的 prop, 它的类型是 Number,并设置了默认值为0
  3. 之后在<template>中, 可以直接使用 count 变量,它是通过父组件传递过来的
  • 父组件 Parent.vue
vue
<script setup>
// 引入 ref 函数, 用于定义响应式数据
import { ref } from 'vue';
// 引入子组件 Child.vue
import Child from './Child.vue'

//使用 ref 函数创建一个响应式变量count, 初始值为0, 该变量将用于传递给子组件
let count = ref<number>(0)
</script>

<template>
  <div id="parent">
    <!-- 将 count 变量传递给子组件 Child -->
    <Child :count="count"/>
  </div>
</template>

上面代码中:

  1. 我们使用了 ref 函数创建了一个响应式变量count,初始值为0,该变量1将用于传递给子组件。
  2. 然后再template中, 通过:count="count"的方式将count变量传递给子组件Child

通过 props, 就实现了一个最简单的父组件到子组件的数据传递

子组件通过$emit方法触发一个自定义事件,并传递给需要的参数。父组件通过在子组件上监听对应的时间,并指定触发事件时的回调函数

子组件 Child.vue

vue
<script setup>
/**
 * 使用 defineProps 函数来定义 Props 的类型和默认值
 * defineProps 不需要引入即可直接使用
 */

  const props = defineProps({
    // 变量 count 是通过父组件传递过来的
    count: {
      type: Number,
      default: 0
    }
  })

  // 使用 defineEmits 函数定义了一个名为 changeParentCount 的自定义事件
  const emit = defineEmits(['changeParentCount'])
  const changeParentCount = () => {
    // 通过 emit 方法触发名为 changeParentCount 的自定义事件,并将参数 5 传递给父组件
    emit('changeParentCount', 5)
  }
</script>

<template>
  <div id="child">
    <h1>count: {{ count }}</h1>
    <button @click="changeParentCount">更新父组件的count</button>
  </div>
</template>

上段代码中:

  1. 我们在<template>的 button中使用 @click="changeParentCount" 添加点击事件监听器,当按钮被点击时,将调用 changeParentCount 方法,触发父组件中的自定义事件

  2. 然后使用 defineEmits函数定义了一个名为 changeParentCount的自定义事件。然后通过 emit方法触发名为 changeParentCount 的自定义事件, 并将参数 5 传递给父组件

父组件 Parent.vue

vue
<script setup>
// 引入 ref 函数, 用于定义响应式数据
import { ref } from 'vue';
// 引入子组件 Child.vue
import Child from './Child.vue'

// 使用 ref 函数创建了一个响应式变量count, 初始值为0,该变量用于传递给子组件
let count = ref<number>(0)

// 这个方法用于处理子组件中触发的自定义事件 changeParentCount 并更新父组件中 count 变量的值
const changeParentCount = (params: number):void => {
  count.value += params
}
</script>

<template>
  <div id="parent">
    <!-- 将 count 变量传递给子组件 Child  -->
    <!-- 监听子组件自定义事件 changeParentCount -->
    <Child :count="count" @changeParentCount="changeParentCount"/>
  </div>
</template>

上面这段代码中:

  • 在template中, 我们通过@符号监听子组件自定义事件changeParentCount 并在父组件中执行名为changeParentCount的方法。它接收一个params的参数,然后更新父组件中count变量的值

  • ref 和 defineExpose

在Vue3中, ref函数除了可以用于定义一个响应式的变量或引用之外,还可以获取DOM组件实例。
而 defineExpose适用于将组件内部的属性和方法暴露给父组件或者其它组件使用。通过这种方式,我们可以定义哪些部分可以从组件外部访问和调用。

子组件Child.vue

vue
<script setup>
  // 引入 ref 函数,用于定义响应式数据
  import { ref } from 'vue';

  // 定义变量和方法
  const msg = ref<string>('我是子组件中的数据');
  const childMethod = ():void => {
    console.log('我是子组件中的方法');
  }

  // defineExpose 对外暴露组件内部的属性和方法,不需要引入,直接使用
  // 将属性 msg 和方法 childMethod 暴露给父组件
  defineExpose({ msg, childMethod })
</script>

上面这段代码中

  1. 我们定义了一个 msg 变量和一个childMethod的方法。

  2. 然后使用 defineExpose函数将msg和childMethod对外暴露出去

这样, 我们在父组件中就可以访问子组件的msg属性或者调用 childMethod 方法

父组件Parent.vue

vue
<script setup>
  // 引入响应式ref
  import { ref } from 'vue';
  // 引入子组件 Child.vue
  import Child from './Child.vue';

  // 获取子组件DOM实例
  const childRef = ref(null);

  // 该方法用于获取子组件对外暴露的属性和方法
  const getChildPropertyAndMethod = () => {
    // 获取子组件对外暴露的属性
    console.log(childRef.value.msg);
    // 调用子组件对外暴露的方法
    childRef.value.childMethod();
  }
</script>

<template>
  <div id="parent">
    <Child ref="childRef"/>
    <button @click="getChildPropertyAndMethod">获取子组件对外暴露的属性和方法</button>
  </div>
</template>

上段代码中

  1. 我们在 template子组件Child身上绑定了一个ref,然后通过const childRef = ref(null) 来获取子组件的DOM实例

  2. 然后给button绑定了一个点击事件 getChildPropertyAndMethod,该方法用于获取子组件对外暴露的属性和方法

  3. 此时,点击按钮,在控制台中可以看到打印出子组件的数据和方法的结果

总结:这种方式之间的通信,主要是在子组件内部,将属性和方法暴露出去,然后再子组件中,先获取到子组件的DOM实例,然后就可以访问子组件的属性和调用子组件的方法了。

  • provide 和 inject

    • provide是在父组件中定义的方法,用于提供数据给子组件
      它接受两个参数,第一个参数是一个字符串或者一个Symbol类型的键,用于识别提供的数据。第二个参数是要提供的数据本身。

    这个数据可以是响应式的对象、响应式的ref、reactive对象、函数等。父组件中使用provide提供数据后,所有的子组件都可以通过inject来访问这些数据。

    • inject时在子组件中使用的方法,用于接受父组件提供的数据。
      它接收一个参数,即要注入的数据的键。

    在子组件中使用inject时,可以直接使用接收到的数据,而不需要再组件的配置选项中声明这些数据。

父组件Parent.vue

vue
<script setup>
/**
 * 引入 ref 函数,用于定义响应式数据
 * 引入 provide,用于提供数据给所有子组件
 */
import { ref, provide } from 'vue'
// 引入子组件1和子组件2
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'

// 定义一个 message 响应式数据
  const message = ref<string>('我是父组件的数据')

  //使用provide 将数据message注入, 并且提供给所有子组件
  provide('message', message)
</script>

<template>
  <div id="parent">
    <Child1/>
    <Child2/>
  </div>
</template>

子组件Child1.vue

vue
<script setup>
import { inject } from 'vue'
//使用inject 获取来自父组件的数据 message
const parentMessage = inject('message')

<template>
  <div id="child">
    <p>子组件1: {{ parentMessage }}</p>
  </div>
</template>
</script>

子组件Child2.vue

vue
<script setup>
import { inject } from 'vue'
//使用inject 获取来自父组件的数据 message
const parentMessage = inject('message')

<template>
  <div id="child">
    <p>子组件2: {{ parentMessage }}</p>
  </div>
</template>
</script>

上面的代码中:

  1. 我们在父组件 Parent.vue 中定义了一个响应式数据 message

  2. 然后使用 provide 将数据 message 提供给所有子组件

  3. 在子组件 Child1 和 Child2 中,我们使用 inject 获取来自父组件的数据 message。

  4. 此时,在页面中,我们可以看到子组件获取到的父组件数据。

除了获取数据,我们也可以修改数据:

vue
<script setup>
  import { inject } from 'vue';

  // 使用 inject 获取来自父组件的数据 message
  const parentMessage = inject('message');

  // 该方法用于更改父组件的message
  const changeParentMessage = () => {
    parentMessage.value = '我更改了message值'
  }
</script>

<template>
  <div id="child">
    <p>子组件1: {{ parentMessage }}</p>
    <button @click="changeParentMessage">更改父组件message</button>
  </div>
</template>

上面这段代码中:

  1. 在子组件 Child1 中,我们定义了一个 changeParentMessage 函数,它更新了来自父组件的 message 值。

  2. 由于 message 在父组件中是响应式的,所以更新后该值将自动反映在父组件的视图中。

  3. 此时,我们点击一下按钮,子组件1和子组件2的值都会被更改。

总结:过使用 provide 和 inject,数据能够在父组件和子组件之间进行传递和共享,实现了跨组件的通信。

v-model可以同事支持多个数据双向绑定。

子组件Child.vue

vue
<script setup>
const emit = defineEmits(['update:name', 'update:age'])

const changeParentMsg = () => {
  emit('update:name','Steven')
  emit('update:age', 36)
}
</script>

<template>
  <div id="Child">
    <button @click="changeParentMsg">更新父组件中的name和age</button>
  </div>
</template>

父组件Parent.vue

vue
<script setup>
  // 引入 ref 函数,用于定义响应式数据
  import { ref } from 'vue';
  // 引入子组件
  import Child from './Child.vue';

  // 定义两个响应式的变量
  let name = ref<string>('Echo');
  let age = ref<number>(26);
</script>

<template>
  <div id="parent">
    <p>父组件Name:{{ name }}</p>
    <p>父组件Age: {{ age }}</p>
    <!-- 使用 v-model 将父组件的 name 和 age 双向绑定到子组件的 name 和 age 上 -->
    <Child v-model:name="name" v-model:age="age"/>
  </div>
</template>

面的代码中:

  1. 我们在父组件内部使用 ref 函数定义了两个响应式变量 name 和 age,并给它们分别赋予初始值。然后在 template 中使用 v-model 将父组件的 name 和 age 双向绑定到子组件的 name 和 age 上。

  2. 在子组件内部,通过 defineEmits(['name', 'age']),我们定义了两个事件:update:name 和 update:age。这样,父组件可以监听并处理这两个事件。然后我们 在 template 中定义了一个按钮,并在 script 中实现 changeParentMsg 的方法。当按钮被点击时,这个方法会调用 emit 方法来派发两个事件。

  3. 通过 emit('update:name', 'Steven'),我们触发了一个名为 update:name 的事件,并传递了一个参数 'Steven'。通过这个事件,我们可以告知父组件更新它的 name 值为 'Steven'。

  4. 通过 emit('update:age', 36),我们触发了一个名为 update:age 的事件,并传递了一个参数 36。通过这个事件,我们可以告知父组件更新它的 age 值为 36。

通过这样的设置,当父组件中的 name 或 age 发生变化时,它们会自动更新到子组件中。同时,当子组件中的 name 或 age 改变时,它们会通过 update:name 和 update:age 事件反馈给父组件,父组件会相应地更新自己的 name 和 age。这就实现了父子组件之间的双向绑定。

在Vue3中,可以使用第三方库mitt来实现组件之间的通信。

mitt是一个简单且强大的事件总线库(类似于Vue2中的 EventBus), 它提供了一种方便的方式来在不同组件中传递事件和数据。

首先 安装 mitt.js

shell
npm install mitt
#     or
yarn install mitt
#     or
pnpm install mitt

接着,创建一个event bus

ts
import mitt from 'mitt'
const bus = mitt()
export default bus

在需要通信的组件中,导入 event bus 对象并进行事件的监听和触发:

组件First.vue

vue
<script setup>
  import mitt from './mitt'

  const emitEvent = () => {
    mitt.emit('updateName', 36)
  }
</script>

<template>
  <div id="first">
    <button @click="emitEvent">更新name和age</button>
  </div>
</template>

组件Second.vue

vue
<script setup>
  import mitt from '../mitt';
  import { ref } from 'vue';

  let name = ref('Echo');
  let age = ref(26);

  mitt.on('updateName', (data) => {
    name.value = 'Steven';
    age.value = data;
  });
</script>

<template>
  <div id="second">
    <p>name: {{ name }}</p>
    <p>age: {{ age }}</p>
  </div>
</template>

上面这个例子中:

  1. 我们创建了一个名为 mitt 的事件总线对象,并在两个组件中进行了引用。

  2. 在 First 组件中,当按钮被点击后,我们使用 mitt.emit 方法触发了一个自定义事件,并传递了一些数据。

  3. 在 Second 组件中,我们使用 mitt.on 方法监听了 updateName 事件,并在回调函数中接收到了传递的数据。然后我们将接收到的数据赋值给相应的属性,在模板中展示出来。

通过这种方式,我们可以在不同的组件中实现通信,First 组件可以通过事件总线发送事件和数据,Second 组件则监听事件并接收到数据进行处理。

  • Pinia

    pinia 是一个为 vue3 设计的状态管理库,类似 Vuex 的设计模式,通过定义 store、状态、getter 和 action,来统一管理应用程序的状态和逻辑。

React中遇到过什么坑

不正确地使用状态更新

直接修改状态(state)而不使用 setState 方法会导致不可预测的结果,因为 React 无法检测到状态的更改。正确的做法是始终使用 setState 来更新状态。

jsx
  // 错误的方式
this.state.counter += 1;

// 正确的方式
this.setState({ counter: this.state.counter + 1 });

使用索引作为 key

在渲染列表时,将索引作为 React 元素的 key 值可能会导致性能问题和不稳定的 UI 行为。最好使用具有唯一标识的属性作为 key。

jsx
  // 错误的方式
{items.map((item, index) => <div key={index}>{item}</div>)}

// 正确的方式
{items.map(item => <div key={item.id}>{item.name}</div>)}

忘记绑定事件处理程序

在类组件中,如果没有将事件处理程序绑定到实例上,可能会导致 this 关键字在事件处理程序中指向 undefined。可以使用箭头函数或在构造函数中绑定方法来解决这个问题。

jsx
  // 错误的方式
  <button onClick={this.handleClick}>Click me</button>

  // 正确的方式
  <button onClick={this.handleClick.bind(this)}>Click me</button>

忘记在条件渲染中处理 null 或 undefined

如果在条件渲染中返回 null 或 undefined,可能会导致 TypeError。请确保在返回之前处理这些情况。

jsx
// 错误的方式
{condition && <div>{undefinedVariable}</div>}

// 正确的方式
{condition && undefinedVariable && <div>{undefinedVariable}</div>}

在渲染函数中定义函数

在渲染函数中定义新的函数,可能会导致组件重新渲染时创建新的函数实例,影响性能。应该将这些函数提升到组件外部或使用 useCallback Hook。

jsx
// 错误的方式
render() {
  return (
    <div onClick={() => this.handleClick()}>Click me</div>
  );
}

// 正确的方式
handleClick = () => {
  // 处理点击事件
};

render() {
  return (
    <div onClick={this.handleClick}>Click me</div>
  );
}

SSR中Node的配置消耗的QPS

△ SSR之前接触并不深刻, 暂时留个坑 通过Next.js 和 Nuxt.js配置时深入了解一下

React的生命周期

在旧版的 React 中,组件生命周期包括三个阶段:挂载阶段(Mounting)、更新阶段(Updating)、卸载阶段(Unmounting)。每个阶段都对应着一系列的生命周期方法,用于在不同的时机执行特定的逻辑。

但在新版的 React 中,推荐使用函数式组件和 React Hooks 来编写组件,这种方式下生命周期方法被简化了。

挂载阶段(Mounting)

  • constructor():构造函数,在组件被创建时调用,用于初始化状态(state)或绑定事件处理方法。

  • static getDerivedStateFromProps(props, state):静态方法,用于根据 props 来更新 state。在组件被创建和每次接收到新的 props 时都会调用。

  • render():必选方法,返回 React 元素的描述。React 会根据此方法的返回值来构建组件的 DOM 树。

  • componentDidMount():组件挂载到 DOM 后立即调用,通常用于执行异步操作、订阅事件等初始化工作。

更新阶段(Updating)

  • static getDerivedStateFromProps(props, state):见上文。 shouldComponentUpdate(nextProps, nextState):决定是否重新渲染组件,可以通过返回 false 来阻止渲染过程。

  • render():见上文。

  • getSnapshotBeforeUpdate(prevProps, prevState):在 DOM 更新之前获取当前 DOM 的快照,通常用于保存一些需要在更新后恢复的状态。

  • componentDidUpdate(prevProps, prevState, snapshot):在 DOM 更新之后立即调用,通常用于执行副作用操作、与其它 JavaScript 框架进行集成等。

卸载阶段(Unmounting)

  • componentWillUnmount():在组件即将被销毁并从 DOM 中移除之前调用,通常用于清理定时器、取消订阅等收尾工作。

另外,还有一些过时的生命周期方法(如 componentWillMount、componentWillReceiveProps、componentDidCatch 等),在新版的 React 中已经被标记为不推荐使用。

需要注意的是,如果使用函数式组件和 React Hooks 编写组件,那么大部分生命周期方法都不再适用。相反,可以使用 useEffect Hook 来代替生命周期方法的功能。

常用的React Hook 和功能

useState

用于管理功能组件中的状态

useEffect

用于在功能组件中执行副作用,例如获取数据或订阅事件

useContext

用于访问功能组件中React 上下文的值

useRef

用于创建对跨渲染保留的元素或值的可变引用

useCallback

缓存回调函数,避免在每次渲染时都创建新的回调函数实例

当回调函数作为prop传递给子组件时,使用 useCallback可以确保子组件在依赖项未变化时不会因为接收到新的函数引起不必要的重新渲染。

在某些情况下,可以配合 useMemo 使用, 将计算逻辑和函数绑定在一起,从而在依赖项不变时只计算一次

useMemo

用于缓存计算值,类似于 useCallback,但是它缓存的是普通数值而不是回调函数

useReducer

用于使用reducer函数的管理状态,类似于Redux的工作方式

useLayoutEffect

类似于 useEffect 但效果在所有DOM突变后同步运行

这些Hook提供了强大的工具,用于管理状态,处理副作用和重新编辑 React功能组件中的逻辑。

什么是虚拟滚动

虚拟滚动列表通过只渲染可视区域的列表项,当用户滚动时,动态计算可视区域的起始索引,然后只渲染这部分列表项,避免了一次加载大量数据从而实现平滑的滚动和效果,并且列表移出可视区域时,虚拟滚动列表会回收对应的DOM元素从而降低内存占用。

虚拟滚动列表的实现原理通常包括以下几个步骤

  1. 计算可视区域的高度以及每个列表项的高度。

  2. 根据滚动条的位置,计算出可视区域的起始索引和结束索引。

  3. 只渲染起始索引和结束索引之间的列表项。

  4. 当滚动条位置变化时,重新计算起始索引和结束索引,并更新渲染的列表项。

lightHouse主要使用什么性能指标

lighthouse主要是用来做性能检测的工具,主要用于分析网络应用和网页,收集现代性能指标并提供对开发人员最佳实践的意见。为lighthouse提供一个需要审查的网址,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告。

主要检测指标

指标说明
性能指标(Performance)[1]
可访问性(Accessibility)监测页面的可访问性与优化建议。
最佳实践(Best Practice)页面是否符合最佳实践。
搜索引擎优化(SEO)页面是否针对搜索引擎结果排名进行了优化。
PWA(Progressive Web App)验证 PWA 的各个方面的性能情况。

[1]: 页面的性能评分,包括首次内容绘制(First Contentful Paint)、首次有效绘制(First Meaningful Paint)、首次 CPU 空闲(First CPU Idle)、可交互时间(Time to Interactive)、 速度指标(Speed Index)、输入延迟估值(Estimated Input Latency)。

评分说明

经过检测,Lighthouse 会对上述五个维度给出一个的评估得分,分值范围0-100,主要分为三个档次,分别用红黄绿三种颜色代表:

  • 0 – 49(慢):红色
  • 50 – 89(平均值): 橙色
  • 90 – 100(快): 绿色

如果没有分数或得分为 0,则很有可能是检测过程发生了错误,比如网络连接状况异常等;如果得分能达到 90 分以上(绿色),则说明网站应用在该方面的评估表现符合最佳实践。

三种使用方式

  • 使用 chrome 调试面板中的 LightHouse (推荐)

  • 使用 chrome 插件扩展

  • 通过命令行使用 LightHouse

跨页面通信使用什么方式呢

跨页面通信是指在Web开发中,不同页面之间进行数据或消息传递的过程。有几种常用的方式可以实现跨页面通信:

URL 参数传递

可以通过URL参数在不同页面之间传递数据。例如,在链接中添加参数,然后在目标页面中读取这些参数。但这种方法适用于传递简单的数据,并且会暴露数据在URL中。

Local Storage

HTML5的Local Storage提供了一种在浏览器中存储数据的方法,这些数据在不同页面之间是共享的。通过在一个页面中写入数据,然后在另一个页面中读取这些数据,实现跨页面通信。但要注意,由于本地存储是同源策略的一部分,因此只能在相同源的页面之间进行通信。

Session Storage

类似于Local Storage,但是会话存储的数据在会话结束后被清除。与Local Storage相比,Session Storage更适合临时存储会话期间需要传递的数据。

Cookies

通过设置cookie,在不同页面之间传递数据。但是,cookies的容量有限,并且会随着每个HTTP请求发送到服务器,可能会影响性能。

PostMessage API

PostMessage API允许在不同的窗口或iframe之间安全地发送消息。通过在发送消息的窗口中调用postMessage()方法,然后在接收消息的窗口中监听message事件来实现跨页面通信。

WebSocket

WebSocket是一种在客户端和服务器之间进行全双工通信的协议。虽然WebSocket通常用于实时通信,但也可以用于不同页面之间的通信,通过在页面上建立WebSocket连接,可以实现实时的跨页面数据传输。

WebSocket 不受同源策略的限制,因此可以用于跨域通信。通过在客户端和服务器之间建立 WebSocket 连接,可以实现双向实时通信。

跨域的方法

跨域是指在浏览器中,当一个网页向不同域名、不同端口或不同协议的资源发起请求时,会受到浏览器的同源策略限制,这样的请求就称为跨域请求。为了解决跨域请求的限制,可以采用以下方法:

JSONP (JSON with Padding)

JSONP 是一种利用 <script> 标签的 GET 请求来实现跨域通信的技术。虽然 JSONP 能解决部分跨域问题,但是只支持 GET 请求,且存在安全性问题(潜在的跨站脚本攻击)。

CORS (Cross-Origin Resource Sharing)

CORS 是一种由 W3C 提出的跨域解决方案。通过在服务器端设置相应的 HTTP 头部信息(例如 Access-Control-Allow-Origin),来授权浏览器跨域访问。CORS 支持各种类型的 HTTP 请求,并且在安全性方面比 JSONP 更可靠。

代理服务器

通过在同源服务器上设置代理服务器来转发请求,从而绕过浏览器的跨域限制。当浏览器无法直接访问目标资源时,可以发送请求到同源服务器上的代理服务器,由代理服务器去请求目标资源,并将结果返回给浏览器。

使用跨域资源共享 (CORS) 的服务器端代理

如果目标服务器支持 CORS,可以使用服务器端代理向目标服务器发起请求,并将结果返回给客户端。这种方法将跨域问题留给了服务器端处理,而客户端则通过与同源服务器通信来解决跨域问题。

JSON Web Token (JWT)

可以在请求头中使用 JWT 来进行跨域身份验证。JWT 是一种基于 JSON 的安全传输令牌,可以在不同域之间传递身份验证信息。

  • Nginx 反向代理:可以使用 Nginx 等反向代理服务器来转发请求,从而实现跨域访问。通过配置 Nginx,在客户端和目标服务器之间建立代理,使得客户端无感知地访问目标服务器资源。

通过iframe标签跨页面通信遇到过什么问题

同源策略限制:同源策略会限制在 <iframe> 中加载的页面与父页面之间的通信,除非它们具有相同的协议、域名和端口。这意味着如果 <iframe> 内嵌的页面与父页面不是同源的话,就无法直接进行跨页面通信。

安全性问题:如果不小心,通过 <iframe> 进行跨页面通信可能会引入安全漏洞,例如跨站脚本攻击(XSS)。如果在 <iframe> 中加载的页面可以受到恶意代码的控制,那么恶意代码可能会利用此 <iframe> 来获取父页面的敏感信息或执行恶意操作。

通信机制限制:通过 <iframe> 进行跨页面通信通常会依赖于一些基于浏览器的通信机制,例如 window.postMessage()。这些机制可能会有一些限制,如不能直接传递复杂的对象,需要在两个页面中都添加相应的监听器等。

性能问题:如果在父页面和 <iframe> 中频繁进行通信,可能会影响页面的性能。特别是在某些情况下,由于通信的频繁性,可能会导致页面变得不稳定或响应变慢。

浏览器兼容性:不同浏览器对于 <iframe> 中的跨页面通信机制的支持程度可能有所不同,需要谨慎考虑兼容性问题。

为了解决这些问题,可以采取一些措施,例如使用安全的通信机制(如 window.postMessage())、确保所有参与通信的页面都受信任、限制通信频率以减少性能影响等。

登录状态不一致 通过URL传递token参数进行登录态校验

还有iframe的宽高设置, 第三方页面的滚动 需要设置是根据那个参数滚动的

△ 微前端的方式

微前端之前没怎么接触过,在此做了一些了解

微前端是一种将前端应用程序拆分为更小、更可管理的部分,并独立开发、测试、部署的方法。在微前端架构中,通常会涉及多个独立的前端应用,它们可以是由不同团队开发、使用不同技术栈构建的。以下是实现微前端的一些常见方式:

组合式架构

  • 基于组件的架构:将前端应用程序拆分为可复用的组件,每个组件负责自己的 UI 和业务逻辑。然后,通过组合这些组件来构建整个应用程序。

  • 模块化的架构:将前端应用程序划分为独立的模块,每个模块负责特定功能或页面。然后,通过模块化的方式组合这些模块来构建整个应用程序。

微服务架构:

  • 独立部署的前端应用:将前端应用程序拆分为多个微服务,每个微服务都是独立开发、测试和部署的。通过服务网关或类似的机制来统一管理这些微服务,使其能够协同工作。

Web Components:

  • 使用 Web Components 技术:Web Components 是一种标准化的 Web 技术,可以将前端应用程序拆分为独立的自定义元素(Custom Elements)、 影子 DOM(Shadow DOM)、模板(Template)和样式(CSS)。通过使用 Web Components,可以实现跨框架和跨应用程序的组件复用和集成。

框架集成:

  • 集成不同的前端框架:将不同的前端框架(如 React、Vue、Angular 等)集成到同一个应用程序中。每个团队可以选择它们喜欢的框架来开发它们的部分,并通过共享数据和通信机制来实现整体应用程序的协同工作。

路由管理:

  • 统一的路由管理:通过统一的路由管理机制来组织和管理不同前端应用程序的路由。可以使用路由中间件或服务来实现统一的路由管理,使得用户能够无缝地在不同的前端应用程序之间进行导航。

有了解过mono-repo开发方式吗

Mono-repo 是指将一个项目的所有代码都存储在一个单独的代码仓库(Repository)中的开发方式。这种开发方式与将不同功能或模块拆分到单独的仓库中(Multi-repo)相对应。Mono-repo 开发方式有以下特点:

  • 集中管理:所有项目的代码都集中存储在一个代码仓库中,便于管理和维护。开发者可以在单个仓库中查找所有相关的代码,而不需要在多个仓库之间切换。

  • 共享代码:不同项目之间可以共享代码和资源,避免了代码重复的问题。共享的代码可以更容易地被复用和维护,同时也有助于保持一致性。

  • 统一构建和部署:所有项目的构建和部署流程可以集中在一个仓库中管理,便于统一配置和管理。这样可以减少重复的配置工作,并且更容易保持构建和部署的一致性。

  • 跨项目协作:开发者可以更容易地在不同项目之间进行协作和交流,因为所有代码都在同一个仓库中。这有助于促进团队内部的合作和知识共享。

  • 版本控制管理:所有项目的代码都在同一个版本控制系统中管理,可以更容易地跟踪和管理代码的变化历史。这有助于保持代码的可追溯性和可维护性。

  • 减少仓库管理成本:相比于多个仓库,维护一个单一的代码仓库可以减少仓库管理的成本,包括权限管理、备份和恢复、仓库迁移等方面。

UseMemo可以在其它地方调用么

在 React 中,useMemo 是一个自定义 Hook,用于在渲染过程中对值进行记忆(memoization)。useMemo 接受一个函数和依赖项数组作为参数,并返回该函数的记忆化版本。记忆化版本只有在依赖项发生变化时才会重新计算,否则将复用上一次计算的结果。

useMemo 返回的记忆化值可以在组件的渲染过程中使用,但通常不建议在其它地方调用。这是因为 useMemo 是一个与 React 生命周期相关的 Hook,它的计算是在组件 渲染过程中触发的,并且它的行为依赖于组件的依赖项数组的变化。直接在组件之外调用 useMemo 可能会导致不一致的结果或不可预期的行为。

如果需要在组件之外的地方进行记忆化计算,可以考虑将记忆化逻辑提取到一个普通的函数中,然后在需要的地方调用该函数。这样可以确保记忆化计算的行为独立于组件的渲染过程,同时也更容易进行测试和维护。

项目中做了哪些性能优化

构建工具有什么性能优化

基本数据类型

在项目中有自己实现过Hook吗,举个例子说明

javascript
import { useState, useEffect } from 'react';

// 自定义 Hook:使用本地存储管理数据
function useLocalStorage(key, initialValue) {
  // 使用 useState 来声明状态变量
  const [value, setValue] = useState(() => {
    // 初始化状态变量时,从本地存储中获取对应键的值
    const storedValue = localStorage.getItem(key);
    // 如果本地存储中有对应键的值,则返回该值;否则返回初始值
    return storedValue ? JSON.parse(storedValue) : initialValue;
  });

  // 使用 useEffect 来监听 value 的变化,并更新本地存储中对应的值
  useEffect(() => {
    // 将 value 转换为 JSON 字符串,并保存到本地存储中
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]); // 当 key 或 value 发生变化时触发 useEffect

  // 返回 value 和一个用于更新 value 的函数
  return [value, setValue];
}

// 使用自定义 Hook 来管理本地存储中的数据
function ExampleComponent() {
  // 使用 useLocalStorage 自定义 Hook 来声明状态变量
  const [name, setName] = useLocalStorage('name', '');

  // 渲染一个输入框,并将其值与本地存储中的 name 绑定
  return (
    <input
      type="text"
      value={name}
      onChange={e => setName(e.target.value)}
      placeholder="Enter your name"
    />
  );
}

在这个例子中,我们定义了一个名为 useLocalStorage 的自定义 Hook,它接受一个键和初始值作为参数,并返回一个状态变量和一个用于更新状态变量的函数。在内部,该 Hook 使用 useState 来声明状态变量,并使用 useEffect 来监听状态变量的变化,并将其保存到本地存 储中。然后,我们在 ExampleComponent 中使用了这个自定义 Hook 来管理输入框中的文本值,将其保存到本地存储中。

人生没有捷径,就像到二仙桥必须要走成华大道。