React

1. 请描述出webpack核心的几个部分,及作用


参考答案
webpack其实是一个平台,在平台中,我们会安装/融入/配置各种打包规则
  + mode:打包模式「开发环境development、生产环境production」
  + entry:入口「webpack就是从入口开始,根据CommonJS/ES6Module模块规范,分析出模块之间的依赖,从而按照相关的依赖关系,进行打包的」
  + output:出口
  + loader:加载器「一般都是用于实现代码编译的」
  + plugin:插件「处理的需求比较多了,例如:压缩、编译HTML、清空打包...+ resolve:解析器
  + optimization:优化项
  + devServer:配合webpack-dev-server,在本地启动Web服务,实现项目预览以及跨域处理...
  + ......

2.介绍一下,webpack中你知道的加载器、插件及其作用


参考答案
常用的插件:
  + html-webpack-plugin:编译HTML
  + clean-webpack-plugin:清空打包内容
  + css-minimizer-webpack-plugin 压缩CSS
  + terser-webpack-plugin 压缩JS
  + mini-css-extract-plugin 抽离CSS样式
常用的加载器:
  + css-loader、style-loader、less-loader、postcss-loader 处理CSS+ babel-loader 编译JS+ file-loader、url-loader 处理图片的
  + ESLint-loader 代码检测的

3.create-react-app是如何处理IE兼容的

tips:需要处理兼容的部分包含:CSS3样式属性、ES6+语法、ES6+内置API

1. 设置browserslist「浏览器兼容列表」
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }

2. 考虑CSS3样式的兼容问题
  + webpack内部的postcss-loader & autoprefixer,根据browserslist,自动给CSS3加相关的前缀

3. 考虑ES6+语法兼容
  + webpack内部的babel & babel-loader & @babel/preset-env & @babel/core,根据browserslist,自动把ES6语法转换为ES5语法!

4. 考虑ES6+内置API的兼容:主要是重写常用的API {@babel/polyfill}
  import 'react-app-polyfill/ie9';
  import 'react-app-polyfill/ie11';
  import 'react-app-polyfill/stable';

4.React项目的“开发环境”下「基于create-react-app创建的项目」,我们需要基于proxy实现跨域代理;目前已知需要代理的服务器地址是:

公司测试服务器:http://192.168.1.123:8081/
开源平台服务器地址:https://open.weixin.com/v2/applet/
请写跨域代理配置的代码!

/* src/setupProxy.js */
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
    // 参考答案
    app.use(
        createProxyMiddleware("/api", {
            target: "http://192.168.1.123:8081",
            changeOrigin: true,
            ws: true,
            pathRewrite: { "^/api": "" }
        })
    );
    app.use(
        createProxyMiddleware("/wx", {
            target: "https://open.weixin.com/v2/applet",
            changeOrigin: true,
            ws: true,
            pathRewrite: { "^/wx": "" }
        })
    );
};

5. 有A/B两个模块,A模块中需要调用B模块提供的方法,请你基于ES6Module模块规范,实现模块的导出和导入「至少两种方案」

方案一:
/* B模块 */
let listeners = [];
const sum=function sum(...params){};
const query=function query(...params){};
export default {
    listeners,
    sum,
    query
};

/* A模块 */
import B from './B';
B.listeners;
B.sum();
B.query();

方案二:
/* B模块 */
export let listeners = [];
export const sum=function sum(...params){};
export const query=function query(...params){};

/* A模块 */
import {listeners,sum,query} from './B';

6. 简单描述:MVC和MVVM两种框架模式的区别

React框架采用的是MVC体系;Vue框架采用的是MVVM体系;

MVC:model数据层 + view视图层 + controller控制层

  • 实现了数据驱动视图的渲染!!
  • 视图中的表单内容改变,想要修改数据,需要开发者自己去写代码实现!!
  • MVC是“单向数据驱动”

MVVM:model数据层 + view视图层 + viewModel数据/视图监听层

  • 实现了数据驱动视图的渲染:监听数据的更新,让视图重新渲染
  • 实现了视图驱动数据的更改:监听页面中表单元素内容改变,自动去修改相关的数据
  • MVVM是“双向数据驱动”

7.简单描述:JSX语法渲染的步骤

  • 首先:基于babel-preset-react-app把JSX编译为React.createElement格式
  • 其次:把createElement方法执行,创建出virtualDOM虚拟DOM对象
  • 最后:基于root.render方法把virtualDOM变为真实DOM「如果是组件更新,是先进行DOM-DIFF,最后把差异部分渲染为真实DOM即可」

8. 根据需求,实现相关的代码

/* 
根据arr数组,循环动态创建出对应的li元素;
li元素的样式是:字体14px,文字黑色;
em元素的样式是:前三行,字体16px,文字红色;后面和li保持一致;
*/
let arr = [{
    id:1,
    title:'标题一'
},{
    id:2,
    title:'标题二'
},...];

const NewsList=function NewsList(){
    return <ul className="news-box">
        {arr.map((item,index)=>{
            let {id,title}=item;
            return <li key={id} style={{
                fontSize:'14px',
                color:'#000'
            }}>
                {index<=2?<em style={{
                    fontSize:'16px',
                    color:'red'
                }}>{id}</em>:<em>{id}</em>}
                {title}
            </li>;
        })}
    </ul>;
};

9. 简单描述:函数组件和类组件的区别?

函数组件

  • 不具备“状态、ref、周期函数”等内容,第一次渲染完毕后,无法基于组件内部的操作来控制其更新,因此称之为静态组件!
  • 但是具备属性及插槽,父组件可以控制其重新渲染!
  • 渲染流程简单,渲染速度较快!
  • 基于FP(函数式编程)思想设计,提供更细粒度的逻辑组织和复用!

类组件

  • 具备“状态、ref、周期函数、属性、插槽”等内容,可以灵活的控制组件更新,基于钩子函数也可灵活掌控不同阶段处理不同的事情!
  • 渲染流程繁琐,渲染速度相对较慢!
  • 基于OOP(面向对象编程)思想设计,更方便实现继承等!

React Hooks 组件,就是基于 React 中新提供的 Hook 函数,可以让函数组件动态化!

10. state 和 props 区别是什么?

props是指组件间传递的一种方式,props自然也可以传递state。由于React的数据流是自上而下的,所以是从父组件向子组件进行传递;另外组件内部的this.props属性是只读的不可修改!
state是组件内部的状态(数据),不能够直接修改,必须要通过setState来改变值的状态,从而达到更新组件内部数据的作用。
参考:state和props的区别

11. 如何实现props的规则校验?

import React from "react";
import PropTypes from 'prop-types';
const Demo = function Demo(props) {
  .... 
};
// 赋值属性默认值
Demo.defaultProps = {
    x: 0
};
// 设置属性规则
Demo.propTypes = {
    x: PropTypes.number,
    title: PropTypes.string.isRequired
};
export default Demo;

12. 简述:如果让你封装一个公共的组件,你会从哪些角度去提高组件的复用性

我们可以基于这样几种方式:

  • 数据信息:可以基于props(属性)传递进来
  • HTML结构信息:可以基于props.children(插槽)传递进来
    还可以基于ref等操作,获取子组件的实例,从而调用其实例上的属性和方法;或者配合React.fowardRef实现ref转发,从而获取子组件内部的元素等!!

13. React 类组件中,常用的生命周期函数有哪些?及意义!

react生命周期函数

14. React.Component 和 React.PureComponent 的区别?

参考答案
PureComponent相比较于Component,自动增加shouldComponentUpdate周期函数,并对原始属性/状态和最新属性/状态进行浅比较!
  + 当属性和状态没有任何改变的情况下,是不会进行组件更新的,起到一个优化的作用!
  + forceUpdate强制更新时,会跳过内部设置的shouldComponentUpdate函数
*/
// 源码可以不写
const shallowEqual = function shallowEqual(objA, objB){
  if (Object.is(objA, objB))  return true;
  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false;
  }
  const keysA = Reflect.ownKeys(objA),
        keysB = Reflect.ownKeys(objB);
  if (keysA.length !== keysB.length) return false;
  for (let i = 0; i < keysA.length; i++) {
    let key = keysA[i];
    if (
      !objB.hasOwnProperty(key) ||
      !Object.is(objA[key], objB[key])
    ) {
      return false;
    }
  }
  return true;
};

// 测试使用
export default class Demo extends React.Component {
    ...
    shouldComponentUpdate(nextProps, nextState) {
        return !shallowEqual(this.props, nextProps) ||
            !shallowEqual(this.state, nextState);
    }
    ...
};

15. 阐述一下,setState方法是同步还是异步?为哈要这样设计?forceUpdate和setState的区别是什么?

React18中,setState的操作是异步的「在任何地方都是」,基于updater更新队列机制,实现了状态的批处理!这样做的好处有:

  • 统一更新,提高视图更新的性能
  • 处理流程更加稳健

在React18之前,我们只在 React合成事件/周期函数期间 批量更新;默认情况下,React中不会对 promise、setTimeout、原生事件处理(native event handlers)等操作中的setState进行批处理「也就是在这些场景中,setState是同步操作!」

基于ReactDOM提供的flushSync函数,可以让更新队列立即刷新(渲染),模拟出同步操作的效果!!

forceUpdate是强制更新,会跳过shouldComponentUpdate的校验,平日开发中,尽可能减少对他的使用!!

16. 什么是React Hooks组件,解决了什么问题?

为函数组件提供状态、生命周期等原本 class 组件中才有的功能,可以理解为通过 Hooks 为函数式组件钩入了 class 组件的特性。
解决问题
1、从组件中提取状态逻辑,解决了在组件之间复用状态逻辑很难的问题;
2、将组件中相互关联的部分拆分成更小的函数,解决了复杂组件的问题;
3、在非class的情况下使用更多的React特性,解决了class组件与函数组件有差异的问题。

17. 列举常见的React Hook函数,及作用「不少于5个」

基础 Hook

  • useState 使用状态管理
  • useEffect 使用周期函数
  • useContext 使用上下文信息

额外的 Hook

  • useReducer useState的替代方案,借鉴redux处理思想,管理更复杂的状态和逻辑
  • useCallback 构建缓存优化方案
  • useMemo 构建缓存优化方案
  • useRef 使用ref获取DOM
  • useImperativeHandle 配合forwardRef(ref转发)一起使用
  • useLayoutEffect 与useEffect相同,但会在所有的DOM变更之后同步调用effect
    部分钩子信息的解释参考

18. useEffect和useLayoutEffect的区别?

useLayoutEffect

  • 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。
  • 可以使用它来读取 DOM 布局并同步触发重渲染。
  • 在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
  • 尽可能使用标准的 useEffect 以避免阻塞视觉更新。

19.简述useMemo和useCallback的作用?

在前端开发的过程中,我们需要缓存一些内容,以避免在需渲染过程中,因大量不必要的耗时计算而导致的性能问题。useMemo可以帮助我们去实现数据的计算缓存!

在父组件调用子组件的时候,如果需要基于属性给子组件传递一个函数,为了让每一次传递给子组件的函数,都是相同的引用地址,我们可以把函数基于useCallback处理一下「useCallback 用于得到一个固定引用值的函数」!这样在子组件中,再基于PureComponent或者React.memo的配合,可以减少子组件非必要的更新操作!!

20. 看代码,分别写出:组件第一次渲染 & 一分钟后点击按钮 各自输出结果

import React, { useState, useEffect, useLayoutEffect } from "react";
const Demo = function Demo() {
    let [x, setX] = useState(10),
        [y, setY] = useState(0);
    useEffect(() => {
        console.log('@1:', x, y);
        return () => {
            console.log('@2:', x, y);
        };
    });
    useEffect(() => {
        console.log('@3:', x, y);
        setTimeout(() => {
            console.log('@4:', x, y);
        }, 10);
    }, []);
    useEffect(() => {
        console.log('@5:', x, y);
    }, [x]);
    useEffect(() => {
        console.log('@6:', x, y);
    }, [y]);
    useLayoutEffect(() => {
        console.log('@7:', x, y);
    }, [x]);
    return <div className="demo-box">
        <span>0</span>
        <button onClick={() => setX(x + 1)}>按钮</button>
    </div>;
};
export default Demo;

//输出结果
第一次渲染:
  @7: 10 0
  @1: 10 0
  @3: 10 0
  @5: 10 0
  @6: 10 0
  @4: 10 0
点击按钮:
  @7: 11 0
  @2: 10 0
  @1: 11 0
  @5: 11 0

21. 简述ref在React组件中的作用

  • 给HTML标签设置ref,可以获取对应的DOM元素
  • 给子组件(类组件)设置ref,可以获取子组件的实例「这样就可以获取子组件中的相关信息了」
  • 给子组件(函数组件)设置ref,需要配合forwardRef和useImperativeHandle,获取子组件内部的元素或者数据/方法等!!

22. 想在Demo「父组件」中,获取Child「子组件」中的:x状态和change方法,该如何编写代码?

import React, { useState, useImperativeHandle, forwardRef, useRef, useEffect } from "react";
const Child = forwardRef(function Child(props, ref) {
    let [x, setX] = useState(0);
    const change = () => {
        setX(x + 1);
    };
    useImperativeHandle(ref, () => {
        return {
            x,
            change
        };
    });
    return <div className="child-box">
        ...
    </div>;
});
const Demo = function Demo() {
    let child = useRef(null);
    useEffect(() => {
        console.log(child.current); //包含需要的信息
    }, []);
    return <div className="demo-box">
        <Child ref={child} />
    </div>;
};
export default Demo;

23. 简述什么是合成事件?以及React中为啥要使用合成事件?

合成事件是围绕浏览器原生事件,充当跨浏览器包装器的对象;它们将不同浏览器的行为合并为一个API,这样做是为了确保事件在不同浏览器中显示一致的属性!

React中的合成事件是基于事件委托实现的;React17及以后,是委托给#root元素;React17以前,是委托给document元素,并且没有实现捕获阶段的派发;

24. 假如ev是合成事件对象,那么请说出:ev.stopPropagation & ev.nativeEvent.stopPropagation & ev.nativeEvent.stopImmediatePropagation 三者的区别?

ev.stopPropagation()
//合成事件对象中的“阻止事件传播”:阻止原生的事件传播 & 阻止合成事件中的事件传播

ev.nativeEvent.stopPropagation();
//原生事件对象中的“阻止事件传播”:只能阻止原生事件的传播

ev.nativeEvent.stopImmediatePropagation();
//原生事件对象的阻止事件传播,相比较于stopPropagation,只不过还可以阻止元素上其它绑定的方法执行

25. 看代码,分析出在React16/18两个版本中,点击inner元素,分别会输出啥?

import React from "react";
class Demo extends React.Component {
    render() {
        return <div className="outer"
            onClick={() => { console.log('outer 冒泡「合成」'); }}
            onClickCapture={() => { console.log('outer 捕获「合成」'); }}>

            <div className="inner"
                onClick={() => { console.log('inner 冒泡「合成」'); }}
                onClickCapture={() => { console.log('inner 捕获「合成」'); }}
            ></div>
        </div>;
    }
    componentDidMount() {
        document.addEventListener('click', () => { console.log('document 捕获'); }, true);
        document.addEventListener('click', () => { console.log('document 冒泡'); }, false);

        document.body.addEventListener('click', () => { console.log('body 捕获'); }, true);
        document.body.addEventListener('click', () => { console.log('body 冒泡'); }, false);

        let root = document.querySelector('#root');
        root.addEventListener('click', () => { console.log('root 捕获'); }, true);
        root.addEventListener('click', () => { console.log('root 冒泡'); }, false);

        let outer = document.querySelector('.outer');
        outer.addEventListener('click', () => { console.log('outer 捕获「原生」'); }, true);
        outer.addEventListener('click', () => { console.log('outer 冒泡「原生」'); }, false);

        let inner = document.querySelector('.inner');
        inner.addEventListener('click', () => { console.log('inner 捕获「原生」'); }, true);
        inner.addEventListener('click', () => { console.log('inner 冒泡「原生」'); }, false);
    }
}
export default Demo;

输出
React18版本中:
document 捕获
body 捕获
outer 捕获「合成」
inner 捕获「合成」
root 捕获
outer 捕获「原生」
inner 捕获「原生」
inner 冒泡「原生」
outer 冒泡「原生」
inner 冒泡「合成」
outer 冒泡「合成」
root 冒泡
body 冒泡
document 冒泡

React16版本中:
document 捕获
body 捕获
root 捕获
outer 捕获「原生」
inner 捕获「原生」
inner 冒泡「原生」
outer 冒泡「原生」
root 冒泡
body 冒泡
outer 捕获「合成」
inner 捕获「合成」
inner 冒泡「合成」
outer 冒泡「合成」
document 冒泡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值