笔记来源

开始 – React (Q1-2,有点傻的问题已经删掉了)

尚硅谷 React 教程(2022 加更,B 站超火 react 教程)_哔哩哔哩_bilibili (Q3-)

Q3:jsx 语法规则详细

  • A3:如下

  • 定义虚拟 dom 时,不要写引号

  • 标签中混入 js 表达式要用{}

  • 样式的类名指定不要用 class,用 className

  • 内联样式,要用style={{key:value}}的形式去写

  • 只有一个根标签

  • 标签必须闭合

  • 标签首字母

    • 若小写字母开头,则将该标签转为 html 中的同名元素,若 html 中无该标签对应的同名元素。则报错
    • 若大写字母开头,react 就去渲染对应的组件,若组件未定义,则报错

Q4:react 创建虚拟 dom 的两种方式

A4:jsx 和 React.createElement()

Q5:真实 dom 与虚拟 dom 是什么与其的区别

是什么?virtual dom 本质是 js 对象形式对 dom 的描述,而页面渲染出的每一个节点则是一个 Real dom。

区别 1:虚拟 dom 不会进行排版和重绘操作,而真实 dom 会频繁重排与重绘。

区别 2:虚拟 dom 的总消耗是“虚拟 dom 增删改 + 真实 dom 差异增删改 + 排版与重绘”,真实 dom 的总消耗是“真实 dom 的完全增删改 + 排版与重绘”

本题题解:https://github.com/febobo/web-interview/issues/181

Q6:react 类组件中 this 的指向问题?构造函数/render 方法/自定义方法

  1. 构造函数一定指向类实例本身

  2. render 方法是 react new 出实例后调用,因此 this 为类实例本身

  3. 类中自定义方法 this 指向,如下 changeWeather 中 this 为 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//A6案例 此时自定义函数中this指向存在问题
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot :false}
}

render() {
return <h1 onClick={this.changeWeather}>今天天气很{this.state ? "炎热" : "凉爽"}</h1>
}

changeWeather(){
//changeWeather放在了哪里? -- Weather原型对象上,供所有实例使用
//通过Weather实例调用changeWeather时,changeWeather中的this就是Weather实例
//但由于一般自定义方法都是绑定在onClick上,因此不是实例调用,只是在绑定时将这个函数给了onClick做回调而已。当用户点击时是直接去堆内存中找到这个函数然后直接调用,因此this在严格模式下一般都为undefined
console.log(this)
}
}

Q7:react 类组件自定义的方法中的 this 为 undefined,如何解决?两种解决方案如下

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
//A7,1(第一种解决方案:强制绑定this:通过函数对象的bind())
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot :false}
//实例对象自身多了一个changeWeather,后续调用即可
this.changeWeather = this.changeWeather.bind(this)
}

render() {
return <h1 onClick={this.changeWeather}>今天天气很{this.state ? "炎热" : "凉爽"}</h1>
}

changeWeather(){
//changeWeather放在了哪里? -- Weather原型对象上,供所有实例使用
//通过Weather实例调用changeWeather时,changeWeather中的this就是Weather实例
//但由于一般自定义方法都是绑定在onClick上,因此不是实例调用,只是在绑定时将这个函数给了onClick做回调而已。当用户点击时是直接去堆内存中找到这个函数然后直接调用,因此this在严格模式下一般都为undefined
console.log(this)
}
}
//A7,2(第二种解决方案:使用赋值语句和箭头函数书写changeWeather
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot :false}
}

render(){
return <h1 onClick={this.changeWeather}>今天天气很{this.state ? "炎热" : "凉爽"}</h1>
}

changeWeather = () => {
console.log(this)
}
}

Q8:什么是 state? 如何在类组件中使用 state?

A8,1 什么是 state?
  1. state 是组件对象最重要的属性,值是对象(可以包含多个 key-value 的组合)

  2. 组件被称为“状态机”,通过更新组件的 state 来更新对应的页面显示(重新渲染组件)

1
2
3
//A8,2如何在类组件中使用state?
//this.state.xxx = this.state.xxx + 1 //错误写法(状态数据,不能直接修改或更新)
this.setState({xxx:!xxx})//正确写法(使用react内置api去更改),注意此api的使用是合并操作

Q9:精简类组件?干掉 constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//A9
class Weather extends React.Component{
//内部状态存在于Weather实例对象
state = {isHot :false}

render() {
return <h1 onClick={this.changeWeather}>今天天气很{this.state ? "炎热" : "凉爽"}</h1>
}

//自定义方法 - 要用赋值语句的形式 + 箭头函数
changeWeather = () => {
console.log(this)
}
}

Q10

  1. 什么是 props?

  2. 在类组件中 props 的使用?

  3. 对 props 的一些限制?

  4. props 简写方式?

  5. 类构造函数中 props 的使用?

  6. 函数组件中 props 的使用?

A10,1 什么是 props?
  1. 每个组件对象都有 props(properties 的简写)属性

  2. 组件标签所有属性都保存在 props 中

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//A10,2在类组件中props的使用?
class Person extends React.Component{
//内部状态存在于Weather实例对象
render() {
const {name,sex,age} = this.props

return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}

//自定义方法 - 要用赋值语句的形式 + 箭头函数
changeWeather = () => {
console.log(this)
}
}

//props传参方法1,要求键值对的形式,如果要传数值,要用{}表示是js中的数值
ReactDOM.render(<Person name="whh" age={18} sex="男"/>,document.querySelector(".test"))

//props传参方法2
const p1 = {name:"whs",age:18,sex:"男"}
ReactDOM.render(<Person {...p}/>,document.querySelector(".test1"))
//A10,3对props的一些限制与指定默认值?需要引入 prop-type.js 对标签属性类型以及必要性的限制
//目前是给Person类自身加属性,在外侧
Person.propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
speak:PropTypes.func//方法限制要用func而不是function关键字
}
//指定默认标签属性值
Person.defaultProps = {
sex:"男"
age:18
}
//A10,4props简写方式?在类中给Person类自身加属性
class Person extends React.Component{
static propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
speak:PropTypes.func//方法限制要用func而不是function关键字
}

//指定默认标签属性值
static defaultProps = {
sex:"男"
age:18
}

//内部状态存在于Weather实例对象
render() {
const {name,sex,age} = this.props

return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}

//自定义方法 - 要用赋值语句的形式 + 箭头函数
changeWeather = () => {
console.log(this)
}
}
//A10,5构造函数中props的使用?接住props并传给super与不接住不传的区别
class Person extends React.Component{
constructor(props){
//接住props并传给super后在可以访问构造函数中this.props,但不传给super则constructor中this.props为undefined,可以分别打开以下两行代码试试看
//super(props)
//super()
console.log(this.props)
}

static propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
speak:PropTypes.func//方法限制要用func而不是function关键字
}

//指定默认标签属性值
static defaultProps = {
sex:"男"
age:18
}

//内部状态存在于Weather实例对象
render() {
const {name,sex,age} = this.props

return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}

//自定义方法 - 要用赋值语句的形式 + 箭头函数
changeWeather = () => {
console.log(this)
}
}
//A10,6函数组件中props的使用?
function Person(props){
const {name,sex,age} = props

return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}

//对标签属性类型以及必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
speak:PropTypes.func//方法限制要用func而不是function关键字
}
//指定默认标签属性值
Person.defaultProps = {
sex:"男"
age:18
}

//props传参方法1,要求键值对的形式,如果要传数值,要用{}表示是js中的数值
ReactDOM.render(<Person name="whh" age={18} sex="男"/>,document.querySelector(".test"))

Q11:ref 的三种创建方法

  1. string 绑定在标签

  2. 回调 Refs (内联与类的绑定函数)

  3. React.createRef()

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//string绑定在标签
class Demo extends React.Component {
showData = () => {
console.log(this.refs.input);
};

render(): React.ReactNode {
return (
<div>
<input ref="input" type="text" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
);
}
}
//回调Refs 内联回调
class Demo extends React.Component {
showData = () => {
const {input} = this
console.log(input)
};

render(): React.ReactNode {
return (
<div>
<input ref={(currectNode) => {this.input = currectNode}} type="text" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
);
}
}
//回调Refs 类的绑定函数
class Demo extends React.Component {
showData = () => {
const {input} = this
console.log(input)
};

savaInput = (c) => {
this.input = c
console.log(c)
}

render(): React.ReactNode {
return (
<div>
<input ref={this.savaInput} type="text" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
);
}
}
//React.createRef()
class Demo extends React.Component {
myRef = React.creatRef()

showData = () => {
const {input} = this
console.log(input)
};

savaInput = (c) => {
this.input = c
console.log(c)
}

render(): React.ReactNode {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
);
}
}

Q12:非受控组件与受控组件的理解?

  1. 非受控组件,一般是用 ref 实现,当用户提交表单时再去获取内容(现用现取节点值,不受我们监听 state 状态)

  2. 受控组件,一般用 onChange 来实现的,当用户改变 input 内容的时候自动调用 onChange 的回调然后去改变 state 中的状态(类似于 vue 中的双向绑定)

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//非受控组件案例 ,现用现取
class Login extends React.Component {
handleSubmit = (event) => {
const { username, password } = this;
if (username != null && password != null) {
alert(
`你输入的用户名是:${username.value}},你输入的密码是:${password.value}`
);
}
};

render(): React.ReactNode {
return (
<form onSubmit={this.handleSubmit}>
<input ref={(c) => (this.username = c)} type="text" name="username" />
<input
ref={(c) => (this.password = c)}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
//onChange与state是受控组件实现的重点
class Register extends React.Component {
state = { username: "", password: "" };

saveUsername = (event: any) => {
this.setState({ username: event.value });
};

savePassword = (event: any) => {
this.setState({ username: event.value });
};

handleSubmit = (event: any) => {
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};

render(): React.ReactNode {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={this.saveUsername} type="text" name="username" />
<input onChange={this.savePassword} type="password" name="password" />
<button>登录</button>
</form>
);
}
}

Q13:什么是高阶函数与函数柯里化?在 react 中的使用场景?

高阶函数:如果一个函数符合下面 2 个规范中的任何一个,那该函数就是高阶函数。

  1. 若 A 函数,接收的参数是一个函数,那么 A 就可以称为高阶函数

  2. 若 A 函数,调用的返回值依然是一个函数,那么 A 就可以称之为高阶函数

常用的高阶函数有:Promise、setTimeout、数组的方法

函数柯里化:通过调用 继续返回函数 的方式,实现多次接受参数最后统一处理的函数编码方式

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
39
40
41
42
43
44
45
46
47
48
49
//A13:函数柯里化,原生js案例

//未使用函数柯里化的求和函数
/* function sum(a,b,c){
return a+b+c
} */

function sum (a) {
return (b) => {
return (c) => {
return a+b+c
}
}
}

const result = sum(1)(2)(3)
//A13 函数柯里化在react中的使用
class Register extends React.Component {
state = { username: "", password: "" };

saveUsername = (event: any) => {
this.setState({ username: event.value });
};

savePassword = (event: any) => {
this.setState({ username: event.value });
};

saveData = (dataType:string) => {
return (event:any) => {
this.setState([dataType],event.target.value)
}
}

handleSubmit = (event: any) => {
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};

render(): React.ReactNode {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={this.saveData("username")} type="text" name="username" />
<input onChange={this.saveData("password")} type="password" name="password" />
<button>登录</button>
</form>
);
}
}

Q14:不用高阶函数和函数柯里化也能接受到监听事件中传入的 dataType 和 event(如下列代码所示)

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
//A13 函数柯里化在react中的使用
class Register extends React.Component {
state = { username: "", password: "" };

saveUsername = (event) => {
this.setState({ username: event.value });
};

savePassword = (event) => {
this.setState({ username: event.value });
};

saveData = (dataType,event) => {
this.setState([dataType],event.target.value)
}

handleSubmit = (event) => {
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};

render(): React.ReactNode {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={(event) => {this.saveData("username",event)}} type="text" name="username" />
<input onChange={(event) => {this.saveData("password",event)}} type="password" name="password" />
<button>登录</button>
</form>
);
}
}

Q15:旧生命周期钩子流程介绍?(React16.x 以及之前)

初始化阶段:由 ReactDOM.render()触发 –初次渲染

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount() –>常用 (一般在这个钩子中做一些初始化的工作,例如:开启定时器、发送网络请求、订阅消息)

更新阶段:由组件内部 this.setState()或父组件 render 触发

  1. shouldComponentUpdate()
  2. componentWillUpdate()
  3. render() –>必须调用一个

卸载组件:由 ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount() –>常用 (一般在这个钩子中做一些收尾的工作,例如:关闭定时器、取消订阅消息)

Q16:新生命钩子的流程介绍?(React17.x-至今)

初始化阶段:由 ReactDOM.render()触发 –初次渲染

  1. constructor()
  2. getDerivedStateFromProps
  3. render()
  4. componentDidMount() –>常用 (一般在这个钩子中做一些初始化的工作,例如:开启定时器、发送网络请求、订阅消息)

更新阶段:由组件内部 this.setState()或父组件 render 触发

  1. getDerivedStateFromProps
  2. shouldComponentUpdate()
  3. render() –>必须调用一个
  4. getSnapshotBeforeUpdate
  5. componentDidUpdate()

卸载组件:由 ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount()

Q17

  1. react/vue 中的 key 有什么作用?(key 的内部原理是什么)

  2. 为什么遍历列表时,key 最好不要用 index?

虚拟 DOM 中 key 的作用:

  1. 简单的说:key 是虚拟 DOM 对象的表示,在更新显示时 key 起着至关重要的作用
  2. 详细的说:当状态中的数据发生变化时,react 会根据【新数据】生成【新的虚拟 DOM】,随后 React 进行【新虚拟 DOM】和【旧虚拟 DOM】的 diff 比较,比较规则如下:
    1. 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
      1. 若虚拟 DOM 中内容没有变,则直接用之前的真实 DOM
      2. 若虚拟 DOM 中内容变了,则生成新的真实 DOM,替换掉页面之前的真实 DOM
      3. 旧虚拟 DOM 未找到与新虚拟 DOM 相同的 key,根据数据创建新的真实 DOM,随后渲染到页面

用 index 作为 key 可能会引发的问题:

  1. 若对数据进行:逆向添加,删除等破坏规则操作:会产生没有必要的真实 DOM 的更新 –> 虽然界面没问题,但效率低
  2. 如果结构中还包含输入类的 DOM:会产生错误 DOM 更新 –> 界面有问题
  3. 注意!如果不存在对数据的逆向添加、逆向删除等破坏顺序操作。仅用了渲染列表用于展示,使用 index 作为 key 是没有问题的

Q18:todoList 案例相关知识点

  1. 拆分组件、实现静态组件,注意 className、style 的写法
  2. 动态初始化列表、如何确定将数据放在哪个组件的 state 中?
    • 某个组件使用:放在其自身的 state 中
    • 某些组件使用:放在他们的共同的父组件 state 中(官方称为:状态提升)
  3. 关于父子之间通信:
    1. 【父组件】给【子组件】传递数据:通过 props 传递
    2. 【子组件】给【父组件】传递数据:通过 props 传递,要求父提前给子传递一个函数
  4. 注意 defaultChecked 和 checked 的区别,类似还有:defaultValue 和 value
  5. 状态在哪里,操作状态的方法就在哪里

Q19:github 搜索案例涉及知识点

  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办

  2. ES6 小知识点:解构赋值 + 重命名

    1
    2
    3
    4
    5
    6
    7
    let obj = {a:{b:1}}

    const {a} = obj

    const {a:{b}} = obj

    const {a:{b:value}} = obj
  3. 消息订阅与发布机制

    1. 先订阅,再发布
    2. 适用于任何组件通信
    3. 要在组件中 componentWillUnmount 中取消订阅
  4. fetch 发送请求(关注分离的设计思想)

Q20:前端路由基本原理

如果想在原生中自己封装一个路由库可以基于 history 封装好的基石(去 bootcdn 上搜索 history)

方法一:直接使用 h5 推出的 history 身上的 api

方法二:hash 值(瞄点)路径带#(兼容性强)

Q21:react 路由的基本使用

温馨提示:目前尚硅谷教的是 5 版本 npm i react-router-dom@5

1.明确好界面导航区和展示区

2.导航区的 a 标签改为 Link 标签

3.展示区些 Route 标签进行路径的匹配

1
<Route path='/xxx' component={Demo}/>

4.<App>的最外侧包裹了一个<BrowserRouter>或者<HashRouter>

Q22:路由组件与一般组件

  1. 写法不同

    1. 一般组件:<Demo />
    2. 路由组件:<Route path="/demo" component={Demo} />
  2. 存放位置不同

    1. 一般组件:component
    2. 路由组件:pages
  3. 接收的 props 不同

    1. 一般组件:写组件标签时传递了什么,就能收到什么
  4. 路由组件:接受 3 个固定的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    history:
    go: f go(n)
    goBack:f goBack()
    goForward:f goForward()
    push:f push(path,state)
    replace:f replace(path,sstate)

    Location:
    pathname:"/about"
    search:""
    state:undefined

    match:
    params:{}
    path:"/about"
    url:"/about"
  • NavLink 可以实现路由链接的高亮,通过 activeClassName 指定样式名

  • 标签内容是一个特殊属性(children)

  • 通过 this.props.children 可以获取标签体中的内容

Q24: react 解决样式丢失问题

  1. 在 public 下的 html 中解决
1
2
3
4
5
6
<!--不要使用的方式-->
<link href="./css/bootstrap.css" rel="stylesheet">
<!--方案一:在本html中直接往css中查找,而不会带上前端路由-->
<link href="/css/bootstrap.css" rel="stylesheet">
<!--方案二:在public相对路径下查找,%PUBLIC_URL%只有在react中有用-->
<link href="%PUBLIC_URL%/css/bootstrap.css" rel="stylesheet">
  1. 使用 router 的 HashRouter 解决,因为#后的网络访问时认为是前端资源,因此不会将前端路由带上。

Q25:react 路由的严格匹配和模糊匹配

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

  2. 开启严格匹配

1
<Route exact={true} path="/about" component={About}/>
  1. 严格模式不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Q26:路由重定向的使用

1
2
3
4
5
6
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<!--重定向兜底使用,上面的都匹配不到就去找to的路径-->
<Redirect to="/about" />
</Switch>

Q27:嵌套路由

  1. 注册子路由时要写上父路由的 path 值

  2. 路由的匹配是按照注册路由的顺序进行的

Q28:向路由组件传递参数(使用优先级从上到下)

  1. params 参数
1
2
3
4
5
<!--路由链接(携带参数)-->
<Link to="/demo/test/tom/18">详细</Link>
<!--注册路由(声明接收)-->
<Route path="/demo/test/:name/:age" component={Test} />
<!--接收参数:this.props.match.params-->
  1. search 参数
1
2
3
4
5
6
<!--路由链接(携带参数)-->
<Link to="/demo/test?name='tom'&age=18">详细</Link>
<!--注册路由(无需声明,正常注册即可)-->
<Route path="/demo/test" component={Test} />
<!--接收参数:this.props.location.search-->
<!--备注:获取到的search是urlencoded编码字符串,需要借助querystring解析-->
  1. state 参数(需要处理保密数据用)
1
2
3
4
5
6
<!--路由链接(携带参数)-->
<Link to={{path:'/demo/test',state:{name:'tom',age:18}}}>详细</Link>
<!--注册路由(无需声明,正常注册即可)-->
<Route path="/demo/test" component={Test} />
<!--接收参数:this.props.location.state-->
<!--备注:刷新也可以正常保留参数-->

Q29:编程式路由导航

借助 this.props.history 对象上的 api 对操作路由跳转、前进、后退

  • this.props.history.push()

  • this.props.history.replace()

  • this.props.history.goBack()

  • this.props.histroy.goForward()

  • this.props.history.go()

Q30:withRouter 的使用

  1. withRouter 可以加工一般组件,让一般组件具备由组件所特有的 api

  2. withRouter 的返回值是一个新组件

Q31:BrowserRouter 和 HashRouter 的区别

  1. 底层原理不一样

    1. BrowserRouter 使用的是 H5 的 history api,不兼容 ie9 及以下版本
    2. HashRouter 使用的是 url 的哈希值
  2. url 表现形式不一样

    1. BrowserRouter 的路径中没有#,例如:localhost:3000/demo/test
    2. HashRouter 的路径中包含#,例如 localhost:3000/#/demo/test
  3. 刷新后对路由 state 参数的影响

    1. BrowserRouter 没有任何影响,因为 state 保存在 history 对象中
    2. HashRouter 刷新后会导致由 state 参数的丢失
  4. 备注:HashRouter 可以用于解决一些路径错误相关的问题

Q32:UI 组件库

国内使用多的 antd

官方文档:https://ant.design/docs/react/introduce-cn

主要掌握:按需引入和自定义主题

国外使用多 material ui

官方文档:https://material.io/

Q33:求和案例_redux 精简版

  1. 去除 Count 组件自身状态

  2. src 下建立:

    1. -redux
      1. store.js
      2. count_reducer.js
  3. store.js

    1. 引入 redux 的 createStore 函数,创建一个 store
    2. createStore 调用时要传入一个为其服务的 reducer
    3. 暴露 store 给你的组件使用
  4. count_reducer.js

    1. reducer 的本质是一个函数,接收:preState,action,返回加工后的状态
    2. reducer 有两个作用:初始化状态,加工状态
    3. reducer 被第一次调用时,是 store 自动触发的,传递的 preState 是 undefined
  5. 在 index.js 中检测 store 中状态的改变,一旦发生改变重新渲染

    1. 备注:redux 只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写

Q34:求和案例_redux 异步 action 版

  1. 明确:延迟的动作不想交给组件自身,想交给 action

  2. 何时需要异步 action:想要对状态进行操作,但是具体的数据靠异步任务返回。

  3. 具体编码:

    1. npm i redux-thunk,并配置在 store 中
    2. 创建 action 的函数不再返回一个一般对象,而是一个正在的函数,该函数中写异步任务
    3. 异步任务有结果后,分发一个同步的 action 去真正操作数据
  4. 备注:异步 action 不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步的 action

Q35:react-redux

使用目的是讲 react 组件和 redux api 分开

react-redux模型图

  1. 明确两个概念:

    1. UI 组件:不能使用任何的 redux 的 api,只负责页面的呈现和交互等
    2. 容器组件:负责和 redux 通信,将结果交给 UI 组件
  2. 如何创建一个容器组件 - 靠 react-redux 的 connect 函数

    1. connect(mapStateToProps.mapDispatchToProps)(组件)
      1. -mapStateToProps:映射状态,返回的是一个对象
      2. -mapDispatchToProps:映射操作状态的方法,返回值是一个对象
  3. 备注:容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入

Q36:求和案例-react-redux 优化

  1. 容器组件和 UI 组件合并为一个文件

  2. 无需自己给容器组件传递 store,给<App/>包裹一个 <Provider store={store}></Provider>即可

  3. 使用 react-redux 后不再需要自己去检测 redux 状态的变化,容器组件自动帮你完成这个工作

  4. mapDispatchToProps 也可以简单的写成一个对象

  5. 一个组件要和 redux”打交道”要经过那几步?

    1. 定义好 UI 组件–不要暴露

    2. 引入 react-redux 的connect方法生成一个容器组件,并暴露,写法如下:

      1
      2
      3
      4
      5
      connect((state) => ({ count: state }), {
      jia: createIncrementAction,
      jian: createDecrementAction,
      jiaAsync: createIncrementAsyncAction,
      })(Count);
    3. 在 UI 组件中通过this.props.xxxx读取和操作状态

Q37:求和案例-react_redux 数据共享版

  1. 定义一个 Person 组件,和 Count 组件通过 redux 共享数据

  2. 为 Person 组件编写:reducer、action,配置 constant 常量

  3. 重点:Person 的 reducer 和 Count 的 Reducer 要使用 combineReducer 进行合并,合并是重点

  4. 交给 store 的是总 reducer,最后注意在组件中取出状态的时候,记得要“取到位”

Q38:纯函数

  1. 一类特别的函数:只要是同样的输入(实参),必须得到同样的输出(返回)

  2. 必须遵从以下一些约束

    1. 不得改写参数数据
    2. 不会产生任何副作用,例如网络请求,输入和输出设备
    3. 不能调用Date.now()或者Math.random()等不纯的方法
  3. redux 的 reducer 函数必须是一个纯函数

Q39:求和案例_react_redux 开发者工具的使用

  1. npm i redux-devtools-extension

  2. .store 中进行配置

    1. import { composeWithDevTools } from "redux-devtools-extension";
    2. createStore(reducers,composeWithDevTools(applyMiddleware(thunk)));

Q40:求和案例_react_redux 最终版

  1. 所有变量名字要规范,尽量触发对象的简写形式

  2. reducers 文件中,编写 index.js 专门用于汇总并暴露所有的 reducer

Q41:Hooks

  1. React Hook/Hooks 是什么?
1
2
Hook是React 16.8.0版本新增的新特性/新语法
可以让你在函数组件中使用state以及其他React特性
  1. 三个常用的 Hook
1
2
3
State Hook:React.useState()
Effect Hook:React.useEffect()
Ref Hook:React.useRef()
  1. State Hook
1
2
3
4
5
6
7
8
(1)State Hook让函数组件也可以有state状态,并进行状态数据的读写操作
(2)语法:const [xxx,setXxx] = React.useState(initValue)
(3)useState()说明:
参数:第一次初始化指定的值在内部作缓存
返回值:包含2个元素的数组,第一个内部当前状态值,第二个为更新状态的函数
(4)setXxx()2种用法
setXxx(newValue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
setXxx(value => newValue):参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
  1. Effect Hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(1)Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2)React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3)语法和说明
useEffect(()=>{
//在此可以执行任何带副作用的操作
return ()=>{
//在此像一些收尾工作,比如清楚定时器/取消订阅等
}
},[stateValue])//如果指定的是[],回调函数只会在第一次render()后执行
(4)可以把 useEffect Hook看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
  1. Ref Hook
1
2
3
(1)Ref Hook可以在函数组件中存储/查找组件内的标签或任意其他数据
(2)语法:const refContainer = useRef()
(3)作用:保存标签对象,功能与React.createRef()一样

Q42:Fragment

使用

1
2
<Fragment></Fragment>
<></>

Q43:Context

理解:一种用于组件间通信的方式,常用于【祖组件】与【后代组件】间通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(1)创建Context容器对象
const XxxContext = React.createContext()
(2)渲染子组件时,外面包裹xxxContext.Provider,通过value属性给后代组件传递数据:
<xxxContext.Provide value={数据}>
子组件
</xxxContext.Provide>
(3)后代组件读取数据:
//第一种方式:使用类组件
static conTextType = xxxContext //声明接收context
this.context //读取context中的value数据

//第二种方式:函数组件与类组件都可以使用(不是特别理解这种写法)
<xxxContext.Consumer>
{
value => { //value就是context中的value数据
要显示的内容
}
}
</xxxContext.Consumer>

Q44:组件优化

Component 的两个问题
  1. 只用执行 setState(),即便不改变状态数据,组件也会重新 render() ==> 效率低

  2. 只要当前组件重新 render(),就会自动重新 render 子组件 ==> 效率低

效率高的做法

只有当前组件的 state 或者 props 发生改变的时候才重新 render()

原因

Component 中的 shouldComponentUpdate()总是返回 true

解决
1
2
3
4
5
6
7
8
9
10
办法1
重写shouldComponentUpdate()方法
比较新旧state或props数据,如果有变化才返回true,如果没有返回false
办法2
使用PureComponent
PureComponent重写了shouldComponentUpdate(),只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较,如果只是数据对象内部数据变化了,返回false
不要直接修改state数据,而是要产生新数据
项目中一般使用PureComponent来优化

Q45:render props

如何向组件内部动态传入带内容的结构(标签)?
1
2
3
4
5
Vue中:
使用slot技术,也就是通过组件标签传入结构 <A><B/></A>
React中:
使用children props:通过组件标签传入结构
使用render props:通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
1
2
3
4
5
<A>
<B>xxx</B>
</A>
{this.props.children}
问题:如果B组件需要A组件内的数据 ==> 找不到
render props
1
2
3
<A render={(data)=><C data={data}></C>}></A>
A组件:{this.props.render(内部state数据)}
C组件:读取A组件传入的数据展示 {this.props.data}

Q46:错误边界

理解:用来捕获后代组件错误,渲染出备用页面

特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件,定时器中产生的错误

使用方式:

getDerivedStateFromError 配合 componentDidCatch

1
2
3
4
5
6
7
8
9
//生命周期函数,一旦后台组件出错,就会触发
static getDerivedStateFromError(error){
return {hasError:true}
}

componentDidCatch(error,info){
//统计页面的错误,发送请求到后台去
console.log(error,info)
}

Q47:组件通信方式

组件间的关系
  • 父子组件

  • 兄弟组件(非嵌套组件)

  • 祖孙组件(跨级组件)

几种通信方式
1
2
3
4
5
6
7
8
9
(1)props:
a.children props
b.render props
(2)消息订阅与发布
pubs-sub、event等
(3)集中式管理
redux、dva
(4)conText
生产者-消费者模式

比较好的搭配方式

1
2
3
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发中使用比较少,封装组件使用多)

React Router 6

前情提要

使用 vite Vite 官方中文文档 初始化项目:yarn create vite my-vue-app --template react

安装 react-router-dom 6:yarn add react-router-dom

概述

与 5.x 版本相比,改变了什么?

内置组件上:移除了 <Switch /> , 新增 <Routes />

语法变化:component={About} 变为 element={<About />}

新增多个 hook:useParams,useNavigateuseMatch

官方明确推荐函数式组件了!

Component

<Navigate />:用于重定向,当这个组件被渲染会修改路劲和切换视图。

props:replace 用于控制跳转模式(push 或 replace,默认是 push)

<Routes> 和 <Route>

<Routes> 组件是 v6 版本用于替代移除的 <Switch />

<Route><Routes> 需要配合使用,且必须要用 <Routes> 包裹 <Route>

<Route> 相当于是 if 语句,如果其路劲与当前的 URL 匹配,则呈现对应的组件

<Route caseSensitive> 属性用于指定:匹配时是否区分大小写。默认为 false

<Route> 也可以嵌套使用,且可配合 useRoutes() 配置“路由表”,但需要通过 <Outlet/> 组件来渲染子路由。

完成一级路由的切换

实现:

  • 用 BrowserRouter 将 App 包裹起来

  • 引入 Link、Routes、Route 相关组件

  • 写组件、引入组件

  • 注册路由

1
2
3
4
5
6
7
8
<Routes>
<Route path="/about" element="{<About" />
} />
<Route path="/home" element="{<Home" />
} />
<Route path="/" element="{<Navigate" to="/home" />
} />
</Routes>
  • 默认重定向到 /Home:使用 <Route path="/" element={<Navigate to="/home" />} />

实现:

  • css 定义高亮效果样式 .active

  • 将 className 写成一个函数,NavLink 切换时或应用初始化时会去调用并由NavLink组件给它传入一个 {isActive:false} 的参数。

  • 通过传入 isActive 参数值切换样式 .active

使用路由表生成路由

引入 useRoutes hook,将 路由规则数组 的方式传递给 hook,让他生成页面的 routes 结构。

1
2
// app.jsx
const elements = useRoutes(routes);
1
2
// app.jsx
<div className="panel-body">{/* 注册路由 */ elements}</div>
1
2
3
4
5
6
7
8
9
10
// router/index.jsx
import Home from '../Home';
import About from '../About';
import { Navigate } from 'react-router-dom';

export default [
{ path: '/about', element: <About /> },
{ path: '/home', element: <Home /> },
{ path: '/', element: <Navigate to="/home" /> },
];
嵌套路由

实现:

html,css 搭建嵌套路由的基本结构。一级路由对应页面中还有二级路由菜单、二级路由对应的页面。

给二级菜单套上 NavLink 或者 Link 供路由跳转到对应规则使用。

在路由表中新增二级路由规则,将匹配规则对应路由组件(页面组件)

路由携带 params 参数

需求 1:点击消息,在下方显示对应的 detail 内容。

实现如下:

先创建三级菜单对应的页面组件 Details

1
2
3
4
5
6
7
8
9
10
11
// Details.jsx 显示
// remember import useParams hook
const { id, title, content } = useParams();

return (
<>
<div>消息id:{id}</div>
<div>消息标题:{title}</div>
<div>消息内容:{content}</div>
</>
);

路由新增三级路由携带参数规则

1
2
// ...二级路由配置
children: [{ path: 'detail/:id/:title/:content', element: <Details /> }],

创建三级菜单 Link 链接并写 to 的携带参数的路径。(对应 “:” 插口写入数据即可)

1
<Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link>
路由 search 参数

需求和需求 1 一样。

修改 Link 链接传递参数方式:类似为 query 参数。?id=1234&&title=kkkk

1
<Link to={`detail?id=${m.id}&&title=${m.title}&&content=${m.content}`}>{m.title}</Link>

修改三级路由参数规则,不需要占位

1
2
// ...二级路由配置
children: [{ path: 'detail', element: <Details /> }],

修改 Details 组件,使用 useSearchParams hook 接受 search 参数,或使用 useLocation hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';
import { useSearchParams } from 'react-router-dom';

export default function Details() {
// setSearch 可用来修改传入的 search 参数
const [search, setSearch] = useSearchParams();
const id = search.get('id');
const title = search.get('title');
const content = search.get('content');

return (
<>
<div>消息id:{id}</div>
<div>消息标题:{title}</div>
<div>消息内容:{content}</div>
</>
);
}
路由 state 参数

需求和需求 1 一样。

需求和需求 1 一样。

修改 Link 链接传递参数方式:类似为 query 参数。?id=1234&&title=kkkk

1
2
3
<Link to="detail" state={{ id: m.id, title: m.title, content: m.content }}>
{m.title}
</Link>

修改三级路由参数规则,不需要占位

1
2
// ...二级路由配置
children: [{ path: 'detail', element: <Details /> }],

修改 Details 组件,使用 useLocation hook 接受 state 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import { useLocation } from 'react-router-dom';

export default function Details() {
const {
state: { id, title, content },
} = useLocation();

return (
<>
<div>消息id:{id}</div>
<div>消息标题:{title}</div>
<div>消息内容:{content}</div>
</>
);
}
编程式路由导航

在组件内使用 useNavigate 钩子,实现编程式路由导航

1
2
3
4
5
6
7
8
9
const navigate = useNavigate()

navigate("/about",{replace:false,state:{
id: "1",title:"标题1",content:"asdf"
}) // 将路由指向 /about 路径下,携带 state 参数

navigate(1) //前进一步

navigate(-1) //后退一步
useInRouterContext()

作用:判断组件是否在路由上下文环境中。

返回值:true or false

useNavigationType()

作用:返回当前的导航类型(用户如何来到当前页面)

返回值:POP PUSH REPLACE

备注:POP 是指直接在浏览器打开这个路由组件(刷新页面)

useOutlet()

作用:用来呈现当前组件中渲染的嵌套路由

useResolvedPath()

作用:给定一个 URL 值,解析其中的:path、search、hash 值

完结撒花!🌹🌹🌹🌹

路漫漫..

要不要使用 useMemo 和 useCallback

哪些情况一个组件会重新渲染?

  1. 组件自己的 state 发生变化的时候
  2. 父组件传递过来的 props 发生变化的时候
  3. 父组件重新渲染了

useMemo 使用场景

如果一些值的计算量很大,那么可以用 useMemo 来做一个缓存,只有依赖变化时才会重新计算,而不是每次重新渲染都进行计算。

useCallback 使用场景

  1. 对于需要传递 “函数” 给子组件的场合,不使用 useCallback 的话,子组件每次都会重新渲染;
  2. 在调用节流、防抖函数时。

三个 Effect

注意:react18 后 useEffect 出现改变,与 useLayoutEffect 差距越来越小。

useInsertionEffect() / useLayoutEffect() / useEffect() 区别:执行时机不同

第一考虑用 useEffect ,当无法满足需求或出现 bug (如:修改原生 dom 样式时出现闪烁的情况使用 useLayoutEffect,动态添加标签性能出现问题使用 useInsertionEffect)

img

其他 hook

useDebugValue、useDeferredValue、useTransition、useId

文档:https://www.lilichao.com/index.php/2022/06/21/%e5%85%b3%e4%ba%8ehook/

视频讲解:https://www.bilibili.com/video/BV1bS4y1b7NV?p=140&vd_source=e597eca0eb022358e07149ce8892a5ef(p139-141)