面试题答案
一键面试减少不必要的组件重新渲染
- 设计思路:
- 使用 React.memo 或 Vue 的 computed 等机制来缓存组件,只有当组件的 props 发生变化时才重新渲染。对于与路由参数无关的公共组件,如导航栏、侧边栏等,尽量使其不随路由切换而重新渲染。
- 在 React 中,对于纯展示组件,可将其包裹在 React.memo 中。例如,导航栏组件如果其 props 不依赖于路由参数,可以这样定义:
const Navbar = React.memo((props) => { // 导航栏渲染逻辑 return <div>{props.title}</div>; });
- 在 Vue 中,对于计算属性相关的组件,利用 computed 来缓存结果。比如侧边栏数据依赖一些不常变化的数据,可以这样定义:
<template> <div> <!-- 侧边栏内容 --> </div> </template> <script> export default { computed: { sidebarData() { // 计算侧边栏数据逻辑 return this.$store.getters.sidebarData; } } }; </script>
- 技术实现方案:
- React:
- 对于路由组件,可以使用 React Router 的
useParams
钩子来获取路由参数,并将其作为 prop 传递给子组件。同时,通过React.memo
包裹子组件,只在参数变化时重新渲染。例如:
import { useParams } from'react-router-dom'; const ProductDetail = React.memo((props) => { const { productId } = useParams(); // 根据 productId 获取产品详情逻辑 return <div>Product Detail: {productId}</div>; });
- 对于路由组件,可以使用 React Router 的
- Vue:
- 在 Vue Router 中,通过
$route.params
获取路由参数。对于组件,可以使用watch
监听路由参数变化,同时利用keep - alive
组件缓存组件实例,避免不必要的重新渲染。例如:
<template> <div> <!-- 产品详情内容 --> </div> </template> <script> export default { data() { return { productDetail: null }; }, watch: { '$route.params.productId': { immediate: true, handler(newId) { // 根据 newId 获取产品详情逻辑 this.fetchProductDetail(newId); } } }, methods: { fetchProductDetail(id) { // 实际的获取产品详情 API 调用 } } }; </script>
- 在 Vue Router 中,通过
- React:
处理路由参数变化时的过渡动画
- 设计思路:
- 使用 CSS 动画或 JavaScript 动画库(如 React 的 React Transition Group、Vue 的 Vue Transition)来实现过渡动画。当路由参数变化时,触发相应的动画效果。
- 对于不同层级的路由切换,可以设计不同的动画效果,比如从商品分类到品牌列表可以使用淡入淡出效果,从品牌列表到产品详情可以使用滑动效果。
- 技术实现方案:
- React:
- 安装
react - transition - group
库。例如,对于产品详情组件的路由切换动画:
import { CSSTransition } from'react - transition - group'; const ProductDetail = React.memo((props) => { const { productId } = useParams(); return ( <CSSTransition in={true} timeout={300} classNames="product - detail - transition" unmountOnExit > <div>Product Detail: {productId}</div> </CSSTransition> ); });
- 在 CSS 中定义
product - detail - transition
的动画效果:
- 安装
- **Vue**: - 使用 Vue 的内置 `<transition>` 组件。例如,在产品详情组件中: ```vue <template> <transition name="product - detail - transition"> <div>Product Detail: {{ $route.params.productId }}</div> </transition> </template> <script> export default {}; </script> <style scoped> .product - detail - transition - enter - from, .product - detail - transition - leave - to { opacity: 0; } .product - detail - transition - enter - active, .product - detail - transition - leave - active { opacity: 1; transition: opacity 300ms ease - in - out; } </style>
- React:
其他性能优化
- 代码分割:
- 设计思路:将不同路由对应的组件进行代码分割,只有在需要的时候才加载相应的代码,减少初始加载的代码量。
- 技术实现方案:
- React:使用动态
import()
语法。例如:
const routes = [ { path: '/product - category', component: React.lazy(() => import('./ProductCategory')), }, { path: '/brand - list/:categoryId', component: React.lazy(() => import('./BrandList')), }, { path: '/product - detail/:productId', component: React.lazy(() => import('./ProductDetail')), }, ];
- Vue:使用
defineAsyncComponent
。例如:
import { createRouter, createWebHistory } from 'vue - router'; const ProductCategory = defineAsyncComponent(() => import('./ProductCategory.vue')); const BrandList = defineAsyncComponent(() => import('./BrandList.vue')); const ProductDetail = defineAsyncComponent(() => import('./ProductDetail.vue')); const routes = [ { path: '/product - category', component: ProductCategory, }, { path: '/brand - list/:categoryId', component: BrandList, }, { path: '/product - detail/:productId', component: ProductDetail, }, ]; const router = createRouter({ history: createWebHistory(), routes, }); export default router;
- React:使用动态
- 预加载:
- 设计思路:在当前页面加载完成后,提前加载下一个可能访问的路由组件,提高用户体验。
- 技术实现方案:
- React:可以使用
IntersectionObserver
来监听页面上某些元素(如导航栏的下一个层级链接)的可见性,当链接可见时,提前加载对应的路由组件。例如:
import React, { useEffect } from'react'; const ProductCategory = () => { useEffect(() => { const brandListLink = document.getElementById('brand - list - link'); const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { import('./BrandList'); } }); if (brandListLink) { observer.observe(brandListLink); } return () => { if (brandListLink) { observer.unobserve(brandListLink); } }; }, []); return ( <div> <h1>Product Category</h1> <a id="brand - list - link" href="/brand - list">Go to Brand List</a> </div> ); }; export default ProductCategory;
- Vue:类似地,可以在组件的
mounted
钩子中使用IntersectionObserver
来实现预加载。例如:
<template> <div> <h1>Product Category</h1> <a id="brand - list - link" href="/brand - list">Go to Brand List</a> </div> </template> <script> export default { mounted() { const brandListLink = document.getElementById('brand - list - link'); const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { import('./BrandList.vue'); } }); if (brandListLink) { observer.observe(brandListLink); } } }; </script>
- React:可以使用