合集:2023年最全前端面试题考点HTML5+CSS3+JS+Vue3+React18+八股文+手写+项目+笔试_参宿7的博客-CSDN博客
⭐表示手写 和 重要程度,*表示了解即可
框架为一二面,面试官尤其喜欢问为什么要用+怎么用
为了简洁,相关文章参考链接在标题里
目录
React :前端组件化框架,不是一个完整的MVC框架,可以认为是MVC中的V(View)
React18
React是用来构造用户界面的JS库
构建组件的方式⭐⭐
React.createClass()、ES6 class 、无状态函数。
props
对外公开属性,只读
传递数据
<body>
<div id = "div"></div>
</body>
<script type="text/babel">
// 函数组件
function Welcome(props){
return (
<div>hello world, {props.msg}</div>
);
}
let element = (
<Welcome msg="hi react" />
);
// 类组件
class Person extends React.Component{
render(){
return (
<ul>
<!--接受数据并显示-->
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.sex}</li>
</ul>
)
}
}
const p = {name:"张三",age:"18",sex:"女"}
ReactDOM.render(<Person {...p}/>,document.getElementById("div"));
//上下等同
ReactDOM.render(<Person name="tom" age = "41" sex="男"/>,document.getElementById("div"));
</script>
传递函数
// 子组件
class Head extends React.Component {
render(){
this.props.getData('子组件的问候~~~')
return (
<div>Head Component</div>
);
}
}
// 父组件
class Welcome extends React.Component {
getData = (data) => {
console.log(data)
}
render(){
return (
<div>
hello world, {this.props.msg}
<br />
<Head getData={this.getData} />
</div>
);
}
}
构造函数获取props
class Foo {
constructor(props){
this.props = props;
}
}
class Bar extends Foo {
constructor(props){
super(props);
console.log(this.props);
}
render(){
console.log(this.props);
return '';
}
}
let props = {
msg: 'hello world'
};
let b = new Bar(props);
b.props = props;
b.render();
多属性传递props
class Welcome extends React.Component {
render(){
//解构
let { msg, username, age } = this.props;
console.log( isChecked );
return (
<div>hello world, {msg}, {username}, {age}</div>
);
}
}
let info = {
msg: 'hi react',
username: 'xiaoming',
age: 20
};
let element = (
<Welcome {...info} />
);
设置props初始值和类型
import PropTypes from 'prop-types'
class Welcome extends React.Component {
static defaultProps = {
age: 0
}
static propTypes = {
age: PropTypes.number
}
...
}
状态提升⭐⭐
将共享状态提升到最近的共同父组件中去,在父组件上改变状态,然后通过props分发给子组件。
state
组件的私有属性,值是对象(可以包含多个key-value的组合)
通过state的变化设置响应式视图,受控于当前组件
class Welcome extends React.Component {
state = {
msg: 'hello',
count: 0
}
handleClick = () => {
this.setState({
msg: 'hi'
});
}
render(){
console.log('render');
return (
<div>
<button onClick={this.handleClick}>点击</button>
{this.state.msg}, {this.state.count}
</div>
);
}
}
let element = (
<Welcome />
);
refs
React操作原生DOM跟Vue框架是类似的,都是通过ref属性来完成的
class Welcome extends React.Component {
myRef = React.createRef()
handleClick = () => {
//console.log(this.myRef.current); // 原生DOM input
this.myRef.current.focus();//获取焦点
}
render(){
return (
<div>
<button onClick={this.handleClick}>点击</button>
<input type="text" ref={this.myRef} />
</div>
);
}
}
总结
props
公开,单向数据流值,父子组件间的唯一通信,不可改
1.每个组件对象都会有props(properties的简写)属性
2.组件标签的所有属性都保存在props中
3.内部读取某个属性值:this.props.propertyName
这有助于维护单向数据流,通常用于呈现动态生成的数据
state
私有(通过Ajax获取回来的数据,一般都是私有数据),
React 把组件看成是一个状态机(State Machines),
只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
refs
当需要获取某一个真实的DOM元素来交互,比如文本框的聚焦、触发强制动画等
当需要操作的元素和获取的元素是同一个时,无需ref鼠标事件 mouseenter与mouseover区别
mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。
mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。
mouseenter 不支持事件冒泡 mouseover 会冒泡
生命周期
生命周期钩子函数就是回调函数,
挂载
- constructor():在 React 组件挂载之前,会调用它的构造函数。(注:如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。)
- render(): class 组件中唯一必须实现的方法。
- componentDidMount():在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。
更新
- render(): class 组件中唯一必须实现的方法。一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。
- componentDidUpdate():在更新后会被立即调用。首次渲染不会执行此方法。
卸载
- componentWillUnmount():在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
可以看到挂载时和更新时都有render
这个方法。这就是为什么state改变的时候,会触发render
重渲染操作。
class Welcome extends React.Component {
state = {
msg: 'hello world'
}
constructor(props){
super(props);
console.log('constructor');
}
componentDidMount = () => {
// react中发起ajax请求的初始操作,在这个钩子中完成
console.log('componentDidMount');
}
componentDidUpdate = () => {
// 等DOM更新后触发的钩子
console.log('componentDidUpdate');
}
componentWillUnmount = () => {
console.log('componentWillUnmount');
}
handleClick = () => {
/* this.setState({
msg: 'hi react'
}); */
//this.forceUpdate();
root.unmount(); // 触发卸载组件
}
render(){
console.log('render');
return (
<div>
<button onClick={this.handleClick}>点击</button>
{ this.state.msg }
</div>
);
}
}
状态提升⭐⭐
状态是 React 组件的核心,是数据的来源,必须尽可能简单。
基本上状态是确定组件呈现和行为的对象。
与 Props 不同,它们是可变的,并创建动态和交互式组件。可以通过this.state() 访问它们。
多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。对子组件操作,子组件不改变自己的状态。
复用组件:Hooks之前复用组件
React Router也用到了这种复用组件。但这两种模式,会增加代码的层级关系
Render Props模式
组件之间使用一个值为函数的 prop 共享代码的简单技术。
class MouseXY extends React.Component {
state = {
x: 0,
y: 0
}
componentDidMount = () => {
document.addEventListener('mousemove', this.move)
}
componentWillUnmount = () => {
document.removeEventListener('mousemove', this.move)
}
move = (ev) => {
this.setState({
x: ev.pageX,
y: ev.pageY
});
}
render(){
return (
<React.Fragment>
{ this.props.render(this.state.x, this.state.y) }
</React.Fragment>
);
}
}
class Welcome extends React.Component {
render(){
return (
<MouseXY render={(x, y)=>
<div>
hello world, {x}, {y}
</div>
} />
);
}
}
let element = (
<Welcome />
);
render属性后面的值是一个回调函数,通过这个函数的形参可以得到组件中的数据,从而实现功能的复用。
HOC高阶组件模式⭐⭐
参数为组件,返回值为新组件的函数
function withMouseXY(WithComponent){
return class extends React.Component {
state = {
x: 0,
y: 0
}
componentDidMount = () => {
document.addEventListener('mousemove', this.move)
}
componentWillUnmount = () => {
document.removeEventListener('mousemove', this.move)
}
move = (ev) => {
this.setState({
x: ev.pageX,
y: ev.pageY
})
}
render(){
return <WithComponent {...this.state} />
}
}
}
class Welcome extends React.Component {
render(){
return (
<div>
hello world, { this.props.x }, { this.props.y }
</div>
);
}
}
const MouseWelcome = withMouseXY(Welcome)
let element = (
<MouseWelcome />
);
纯函数
不更改在调用前的对象或变量(如果改变了,就叫副作用)
输入相同,则输出相同
未更改的组件来 跳过渲染,以提高性能
Hooks
自定义Hook
实现函数组件复用
let { useState, useEffect } = React
let useMouseXY = () => {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
useEffect(()=>{
function move(ev){
setX(ev.pageX)
setY(ev.pageY)
}
document.addEventListener('mousemove', move)
//如果返回回调函数:相当于componentWillUnmount,会在组件卸载的时候执行清除操作。
return () => {
document.removeEventListener('mousemove', move)
}
}, [])//如果传空数组:相当于componentDidMount
return {
x,
y
}
}
let Welcome = ()=>{
const {x, y} = useMouseXY()
return (
<div>
hello Welcome, {x}, {y}
</div>
)
}
Router
路由是根据不同的url地址展示不同的内容或页面,是SPA(单页应用)的路径管理器,
用于开发单页 Web 应用
1.一个路由就是一个映射关系(key:value)
2.key为路径, value可能是function或component
Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。
路由模式(同Vue)
React中的路由模式跟Vue中一样,分为history和hash模式。默认是hash
Hash模式
hash——即地址栏URL中的#符号(此hash不是密码学里的散列运算)。比如在 http://localhost:8080/#/donate 中,hash的值就是#/donate,我们在浏览器中可以通过BOM中的window.location.hash来获取当前URL的hash值
注:BOM(Browser Object Model) 是指浏览器对象模型,BOM由多个对象组成,其中代表浏览器窗口的Window对象是BOM的顶层对象,其他对象都是该对象的子对象。
history模式
通过在host后,直接添加斜线和路径来请求后端的一种URL模式。
图中pathname变量为/donate,而hash值为空。
原理
- hash通过window.addEventListener监听浏览器的onhashchange()事件变化,查找对应的路由规则
- HTML5的history API监听URL变化,所以有浏览器兼容问题
区别
- 是否向后端传参:
在hash模式中,我们刷新一下上面的网页,可以看到请求的URL为http://localhost:8080/,没有带上#/donate,说明hash 虽然出现 URL 中,#后面的内容是不会包含在http请求中的,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
在history模式中,刷新一下网页,明显可以看到请求url为完整的url,url完整地请求了后端:
前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误
在React中
-
history模式:createBrowserRouter
IE9及以下不兼容,需要由web server支持,在web client这边window.location.pathname被react router解析
-
hash模式:createHashRouter
不需要由web server支持,因为它的只有‘/’path需要由web server支持,而#/react/route URL不能被web server读取,在web client这边window,location.hash被react router解析
history的好处是可以进行修改历史记录,并且不会立刻像后端发起请求。不过如果对于项目没有硬性标准要求,可以直接使用hash模式开发。
基础路由搭建
import { createBrowserRouter, createHashRouter } from 'react-router-dom'
//路由表
export const routes = [];
//路由对象
const router = createBrowserRouter(routes);
export default router;
接下来让路由配置文件与React结合,需要在主入口index.js进行操作,如下:
import { RouterProvider } from 'react-router-dom'
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
路由表的配置字段如下:
-
path:指定路径
-
element:对应组件
-
children:嵌套路由
//路由表
export const routes = [
{
path: '/',
element: <App />,
children: [
{
path: '',
element: <Home />
},
{
path: 'about',
element: <About />,
children: [
{
path: 'foo',
element: <Foo />,
},
{
path: 'bar',
element: <Bar />,
}
]
}
]
}
];
接下来就是显示路由区域,利用<outlet>组件占位,表明子路由渲染的位置
import React from "react";
import { Outlet, Link } from 'react-router-dom'
function App() {
return (
<div className="App">
<h2>hello react</h2>
<Link to="/">首页</Link> | <Link to="/about">关于</Link>
<Outlet />
</div>
);
}
export default App;
可以看到 <Link>组件用于声明式路由切换使用。同样<outlet>组件也可以给嵌套路由页面进行使用,从而完成二级路由的切换操作。
import React from 'react'
import './About.scss'
import { Outlet, Link } from 'react-router-dom'
export default function About() {
return (
<div>
<h3>About</h3>
<Link to="/about/foo">foo</Link> | <Link to="/about/bar">bar</Link>
<Outlet />
</div>
)
}
重定向路由
访问的URL跳转到另一个URL上,从而实现重定向的需求。
<Navigate>组件就是实现重定向需求的组件。
import { createBrowserRouter, createHashRouter, Navigate } from 'react-router-dom'
children: [
{
index: true,// 默认路由
element: <Navigate to="/about/foo/123" />,
},
{
path: 'foo',
element: <Foo />
},
{
path: 'bar',
element: <Bar />
}
]
自定义全局守卫
全局守卫:包裹根组件,访问根组件下面的所有子组件都要先通过守卫进行操作
在React里面,它不像Vue一样,为我们提供和很多方便的功能,一些功能都需要自己去进行封装比如说路由拦截
在/src/components/BeforeEach.jsx下创建守卫的组件。
import React from 'react'
import { Navigate } from 'react-router-dom'
import { routes } from '../../router';
export default function BeforeEach(props) {
if(true){
return <Navigate to="/login" />
}
else{
return (
<div>{ props.children }</div>
)
}
}
根据判断的结果,是否进入到组件内,还是重定向到其他的组件内。
调用BeforeEach.jsx,index.js通过路由配置文件引入,如下:
export const routes = [
{
path: '/',
element: <BeforeEach><App /></BeforeEach>//包裹根组件APP
}
]
动态路由
根据不同的URL,可以访问同一个组件。在React路由中,通过path字段来指定动态路由的写法。
foo/xxx都能访问到Foo组件,本身就有实现了动态路由
import { Outlet, Link } from 'react-router-dom'
export default function About() {
return (
<div>
<Link to="/about/foo/123">foo 123</Link> | <Link to="/about/foo/456">foo 456</Link>
</div>
)
}
{
path: 'foo/:id',
element: <Foo />
}
id
就是变量名,可以在组件中用useParams
来获取到对应的值。
import { useParams } from 'react-router-dom'
export default function Foo() {
const params = useParams()
return (
<div>Foo, { params.id }</div>
)
}
Redux状态管理库⭐⭐
React18新增特性⭐⭐
React脚手架
开始一个react项目,不用手动配置,直接开发
MVC、MVP、MVVM模式⭐⭐⭐
MVC (Model View Controller)
- Model(模型):提供数据
- View(视图):显示数据
- Controller(控制器):用户交互
【优点】
耦合性低,方便维护,可以利于分工协作 重用性高
【缺点】
使得项目架构变得复杂,对开发人员要求高
- key 对 DOM 操作的代价非常高
- 程序运行缓慢且效率低下
- 内存浪费严重
- 由于循环依赖性,组件模型需要围绕 models 和 views 进行创建
MVP(Model View Presenter)
从MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,
MVVM (Model View View Model)
视图和业务逻辑分开。
View视图层,Model 数据模型,ViewModel 是它们双向绑定的桥梁,自动同步更新
【优点】
相比mvp各层的耦合度更低,一个viewmodel层可以给多个view层共用(一对多),提高代码的可重用性。
*耦合度:模块间依赖的程度。
【缺点】
因为使用了dataBinding,增加了大量的内存开销,增加了程序的编译时间,所以适合轻量级项目。
数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题
vue : MVVM 框架,由 MVC 发展而来
React :前端组件化框架,不是一个完整的MVC框架,可以认为是MVC中的V(View)
React单向数据流:只能由数据层的变化去影响视图层的变化
Vue双向数据绑定