初识 React
是什么
用于构建用户界面的js库,是一个将数据渲染为html视图的开源js库,由facebook开发的
为什么要学习react
原生js操作dom繁琐,效率低,
使用js直接操作dom,浏览器会进行大量的重绘重排
原生js没有组件化编码方案,代码复用率低
特点
- 采用
组件化
模式、声明式编码
,提高开发效率及组件复用率。 - 在
React Native
中可以使用React语法进行移动端开发 - 使用
虚拟DOM
+优秀的Diffing
算法,实现dom的复用,尽量减少与真实DOM的交互。

hello_react
1 |
|
- 上述三个js文件一定要按顺序引入
- 我们引入
react.development.js
后,在全局就会出现React
对象,引入react-dom.development.js
后,在全局就会出现ReactDOM
对象 - script标签的类型一定要是是
text/babel
,因为我们写的是jsx代码,然后借助**浏览器的babel
**进行代码转换
jsx
是什么
jsx是javascript
和xml
的缩写,xml早期用于存储和传输数据。现在已经被json替代
1 | <student> |
为什么在react中使用jsx不使用js?
创建虚拟dom
两种语言创建虚拟dom的语法不同
jsx
1
const VDOM = <h1 id="title"><span>Hello,React</span></h1>
js
1
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))
显然,使用jsx创建虚拟dom更简单。
虚拟dom是什么
我们知道使用jsx创建虚拟dom更简单,那虚拟dom是什么呢?
虚拟dom本质就是一个js对象,是对真实dom的高度抽象,我们可以通过输出虚拟dom和真实dom来比较分析它们的区别
1 | <script type="text/babel"> |
我们可以观察到,虚拟dom和真实dom都是对象,但是真实dom身上的属性,比虚拟dom上的属性多得多。
jsx语法规则
1 | <!DOCTYPE html> |
基础规则
定义虚拟dom的时候,不要写引号
在标签中混入js表达式的时候要用
{}
,一定注意区分:js语句
与js表达式
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,下面这些都是表达式:
- a
- a+b
- demo(1),函数调用,值是函数的返回值
- arr.map(),函数调用,值是函数的返回值
- function test(){},函数定义
- 语句(代码):下面这些都是语句(代码):
- if(){}
- for(){}
- switch(){case:xxxx}
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,下面这些都是表达式:
标签必须闭合
只有一个根标签,类似vue2
标签首字母如果是小写的,则将该标签转换为html中的同名标签,如果html没有对应的标签则报错
标签首字母如果是大写的,react就去渲染对应的组件,若该组件没有定义则报错。
样式控制
样式的类名不要用
class
,而要用className
,为了避开es6的class关键字内联样式要用
style={{key:value}}
的形式书写1
2const styles = {color:'red',font-size:'12px'}
<span style={styles}>this is a span</span>
在jsx中实现条件渲染
1 | <div>{flag && <span>this is a span</span>}</div> |
受控组件
在 React 中,当您为
<textarea>
或其他表单元素(如<input>
)设置value
属性时,React 会将其视为一个受控组件
。受控组件的值完全由 React 控制,而不是由 DOM 自身控制,这一点在Vue中是不存在的。
如果您只设置了
value
,而没有提供一个更新状态的机制(例如onChange
),React 会认为该值是固定的,不允许用户直接修改。当用户尝试输入时,React 会检查
value
的值是否发生变化。如果
value
是一个固定的字符串(或未更新的状态值),React 会强制<textarea>
的值保持不变,从而阻止用户的输入。
1 | export default function BC() { |
下面的代码,会出现无论如何在textarea
输入值,都无法修改value
值的问题,因为content
只是一个普通的变量,在textarea
输入值,确实能触发onChange
修改content
的值,content
的值更新了,但是不会重新给value
属性赋值,所以value
的属性一直不会改变。但是如果content是状态中的数据 ,使用setContent(e.target.value)
修改content,不仅会修改值,还会重新渲染,重新给textarea
的value
属性赋值。
1 | export default function BC() { |
其实还有一种方法就是使用defalultValue
1 | export default function BC() { |
但是这样其实就是第一个渲染的时候给defaultValue
赋值,后续defaultValue
就相当于不存在了,只能生效一次。后续在textarea
输入值,确实能成功输入,也确实会修改content
的值,但是content
的值的改变,react是无法发现的。
组件与模块化
模块化
将复杂的js文件拆分成一个一个js文件,每个js文件就是一个模块
组件化
组件是能实现局部功能的代码和资源的集合(html,css,js,images…)
组件化
函数式组件
1 | <script type="text/babel"> |
要注意的点包括:
- 一个函数想要被正确识别为函数式组件,而不是普通函数,函数名必须大写(大驼峰)。函数名就是组件名,要符合组件名的规范,首字符必须大写。
- 函数必须有返回值,返回一个
虚拟dom
ReactDOM.render
的第一个参数必须是组件标签,而不是组件名。- 函数式组件可以接收传入的props,因为函数可以传参
类式组件
1 | <script type="text/babel"> |
要注意的点包括:
- 如果一个类想要成为
类式组件
,必须继承React.Component
类 - 这个类必须实现
render
方法,且这个方法必须有返回值
组件实例的3大属性

构造器问题
组件实例上的所有属性,都是React.Component
类的构造函数
初始化的,因为我们定义的类并没有书写构造器。然而即便我们不书写构造器,也会默认添加一个构造器:
1 | class Child extends React.Component {} |
关于这一点,我们举个例子说明:
1 | class Parent { |
要注意的是,使用super
关键字调用父类构造函数,和直接调用父类构造函数的很重要的区别在于,使用super
关键字调用父类构造函数,构造函数的this指向子类实例,所以能成功初始化子类实例。
延伸到React类式组件:
1 | class Component { |
state
可以看到上面实例的state属性是null
,如果我们想要修改这个属性,就必须在自定义的组件
(类)中添加构造函数。
初始案例
1 | <script type="text/babel"> |
要注意的包括以下几点:
- 类中的方法都在局部开启了严格模式
render
方法是通过组件实例调用的,不过这个组件实例不是我们手动创建的,这个方法也不是我们手动调用的。- 在react中
onClick
不能写成onclick
,虽然在js原生语法中就是写做onclick
state的精简
在上述代码中我们为了初始化state
等操作,引入了构造函数,其实我们可以直接省略构造函数
1 | <script type="text/babel"> |
方法精简
- 我们把
changeWeather
函数写成a=1
的形式,这样的话,这个方法就不会被挂载到原型对象上了,而是组件实例本身上 - 如果我们不写作
箭头函数
,由于这个函数在点击事件触发后,还是会直接调用,this
指向undefined
- 但是如果我们写作箭头函数,
this
的指向就是组件实例(虽然不知道为什么),无论被如何调用 - 因为我们定义的方法常常是被用做回调函数的,所以这种写法:
a = ()=>{}
是被推荐的
setState
- 在react中,我们不能直接修改state,否则虽然数据会改变,但是react无法监听到,视图也不会更新,我们必须使用
this.setState
方法,修改数据并通知视图更新。 - 我们可以试着直接打印state,会发现react中的state只不过是普通的数据,不像vue那样是响应式的。
props
1 | <script type="text/babel"> |
- 在react中,也是通过给
组件标签
添加属性
来实现给组件传值的,然后这些属性会被收集到组件实例的props属性中,值为一个对象。 - props可以传递任意类型的数据,数字,字符串,布尔值,数组,对象,函数,jsx
- 子组件只能读取props中的数据,不能直接进行修改,它是只读的,父组件的数据只能由父组件修改
下面的代码则展示了如何快速的给组件传值
1 | let obj = {name:"jerry",age:19,sex:"男"} |
限制传入组件值的类型
1 | ReactDOM.render(<Person name="jerry" age="19" sex="男"/>, document.getElementById('test1')); |
通过上述方式传入的name,age等属性值,它们的类型都是字符串,后续如果需要在模板中实现:
1 | <li>年龄:{age+1}</li> |
的效果,得到的就是191
,即字符串拼接。
想要对传入组件的值的类型进行限制,并添加默认值,需要添加如下代码:
1 | //对标签属性进行类型、必要性的限制 |
其中的PropTypes
对象是通过,引入react/prop-types.min.js
文件后,出现的全局的对象。
可以看出在react中限制给组件传入的值的类型是非常麻烦的。
其实我们可以把 Person.propTypes = {...}
,和Person.defaultProps = {...}
的操作写在类的内部,从而简化代码。本质都是在类的构造函数上加属性(typeof Class A = 'function'
)
1 | <script type="text/babel"> |
我们之前在学习react组件的构造器的时候也发现了props的身影:
1 | constructor(props) { |
其中的props
,就是传递给组件的所有参数组成的对象,如果不写super(props)
,就无法在构造函数中通过this.props
访问到传递给组件的参数对象,但是最终this.props
还是会被正确初始化。
我们可能认为,因为函数式组件内部没有this,所以不存在组件实例的三大属性。但是因为函数可以传参,我们可以在函数式组件中拿到props:
1 | function Person(props){ |
同时我们也观察到到,类式组件的构造函数中(如果书写的话),传入的参数也包含props。
refs
this.refs
1 | class Demo extends React.Component { |
在react中,我们没有必要直接通过document.querySelector
来获得dom对象,我们直接给组件标签添加ref
属性,并传入一个唯一的值key,然后这个标签对应的dom元素,就可以通过this.refs.key
访问到,这一点和vue2中的语法是很像的(在vue2中是通过this.$refs.key
拿到)。这种写法因为需要借助this,所以不能在函数式组件中使用。
回调函数
然而这种给ref属性赋值字符串的语法,是不被推荐的,因为被认为是效率低的,推荐的写法是传入回调函数
,在传入的回调函数的参数中能拿到对应的dom。
1 | render() { |
不过要注意的是,如果 ref 回调函数是以内联函数的方式定义的(比如上面的例子),在更新过程中,它会被执行两次,第一次传入参数 null
,然后第二次会传入 DOM 元素。这是因为在每次渲染时,会创建一个新的函数实例,所以 React 清空旧的 ref(传入null), 并且设置新的,不过这是无关紧要的,开发过程中仍然可以使用。
1 | <script src="./react/react.development.js"></script> |
为了避免这种问题,我们可以传入一个在类中已经定义好的,挂载到组件实例上的函数。
React.createRef()
然而,react最推荐的方式是使用React.createRef()
先定义一个容器,然后再把dom放入容器中:
1 | myRef = React.createRef() |
最后通过this.myRef.current
就能访问到dom,要注意的是每个容器只能放一个dom,感觉不如document
….
事件处理
- 通过
onXxx
属性指定事件处理函数(注意大小写) - React 使用的是自定义(合成)事件,而不是使用的原生 DOM 事件
- React中的事件是通过
事件委托
方式处理的(委托给组件最外层的元素) - 通过 event.target 得到发生事件的 DOM 元素对象
受控组件和非受控组件
- 受控组件就是表单组件值改变的时候就更新值到state
- 而非受控组件就是通过ref获取表单组件dom,然后需要的时候通过dom.value来获得值
生命周期(旧)
1 | <script type="text/babel"> |
不能在render函数中开启定时器,通过this.setState
来修改状态,因为这个操作会触发render,从而导致无限调用render,开启多个定时器,所以我们把开启定时器的代码写在生命周期函数中。简单的来说,不能在render函数中书写可能触发render的操作。

shouldComponentUpdate
:这个钩子的作用就类似一个阀门,如果我们在组件中不写这个钩子,这个钩子默认存在且返回值为true
。如果这个钩子的返回值为false,那么数据更新了也不会调用render方法更新视图forceUpdate
方法的效果是强制组件更新,即便数据没有改变,也不会经过shouldComponentUpdate
钩子的判断具有父子组件的页面初次加载:
- 先按顺序执行父组件的
constructor
,componentWillMount
,render
钩子,执行render
函数遇到子组件标签的时 - 再按顺序执行子组件的
constructor
,componentWillMount
,render
,componentDidMount
钩子 - 直到子组件的dom创建好了(
componentDidMount
),父组件再执行后续代码,挂载dom(componentDidMount
) - 也就是说,页面初次加载的时候,父组件会等待子组件DOM挂载完毕后,再进行DOM挂载操作
- 也就是说,页面初次加载的时候,父子组件中和
update
有关的钩子都不会被执行
- 先按顺序执行父组件的
父组件更新数据:
- 当父组件间中的数据更新(调用
setState
),父组件会依次调用shouldComponentUpdate
,componentWillUpdate
,render
钩子 - 调用到
render
钩子的时候,因为render
中包含了子组件标签,所以开始触发子组件更新 - 然后子组件依次调用:
componentWillReceiveProps
,shouldComponentUpdate
,componentWillUpdate
,render
,componentDidUpdate
钩子 - 再执行父组件的
componentDidUpdate
的钩子。 - 不是,就连组件更新父组件也要等待子组件先更新完?
- 也就是说,父组件如果不是某个组件的子组件,就不会触发
componentWillReceiveProps
钩子。
- 当父组件间中的数据更新(调用
子组件更新数据:
- 子组件数据更新,只会调用子组件
shouldComponentUpdate
,componentWillUpdate
,render
,componentDidUpdate
钩子,父组件不会重新渲染。
- 子组件数据更新,只会调用子组件
拓展:
- 其实如果父组件并没有给子组件传值,父组件重新
render
,也会导致子组件重新render。 - 我们可以看到,react的所有生命周期钩子都包含
component
,且几乎都以它开头,这是否有点繁琐呢?相比于vue;去除掉component
,我们可以观察到,willMount
就是vue中的beforeMount
,DidMount
就是vue中的mounted
; - 对于更新部分,在vue中不存在
shouldComponentUpdate
这样控制是否更新的钩子,而willUpdate
就是vue中的beforeUpdate
,而DidUpdate
就是vue中的updated
。 - 对于render,在vue中,虽然
render
函数出现频率没有react中的那么高,但是它们的作用都是一致的,就是创建虚拟dom,而且它们被调用的时机都是相似的,在WillMount
和DidMount
之间,或者在WillUpdate
和DidUpdate
之间。
- 其实如果父组件并没有给子组件传值,父组件重新

案例代码:
1 |
|
生命周期(新)
- 在react的17.x版本后,
componentWillMount
,componentWillUpdate
,componentWillReceiveProps
这三个钩子(3个will)已经不推荐使用 - 因为它们是不重要的,而且经常被错误的使用,并且还可能在未来的异步渲染中引发更多问题
- 因此17.x版本后这个三个钩子必须加上
UNSAFE_
前缀,这并不意味这这三个钩子是不安全的,只是为了加长单词长度让人们尽可能的少用它们,并且这三个钩子在未来很可能被删除。

除此以外,还添加了2个新的生命周期钩子:getDerivedStateFromProps
和getSnapshotBeforeUpdate
,虽然这2个钩子在开发过程这几乎没有用武之地,但是还是需要了解的。

getDerivedStateFromProps
1 | static getDerivedStateFromProps(props,state){ |
这个钩子的中文意思就是,从props获取派生的状态,当你的state在任何的时候都取决于props,那么这个钩子才有作用。
而且这个钩子前必须使用
static
修饰,说明它其实会挂载到类上面,也就是构造函数上去。这个静态方法会在组件实例化,以及每次组件更新之前被调用,它接收两个参数:
props
(新的属性)和state
(当前的状态)。该方法的目的是根据传入的新属性,来决定是否需要更新组件的状态。
getSnapshotBeforeUpdate
这个钩子的中文意思是,在更新之前获取快照
1 | //在更新之前获取快照 |
这钩子的返回值,会被传递给componentDidUpdate
钩子,也就是snapshotValue
。
1 | <script type="text/babel"> |
diff算法
diff算法的最小比较单位是结点
react/vue中的key有什么作用?(key的内部原理是什么?)
简单的说:key是虚拟DOM对象的唯一标识,在更新显示时key起着极其重要的作用。
详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后进行新旧虚拟dom的比较,比较规则如下:
存在与新虚拟dom的key值相等的旧虚拟dom
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中旧的真实DOM
不存在与新虚拟dom的key值相等的旧虚拟dom,根据新的虚拟dom,创建新的真实DOM,随后渲染到到页面
使用每条数据的唯一标识作为key,有利于diff算法,有利于提高dom的复用率。
为什么遍历列表时,key最好不要用index?
如果逆序添加数据,会造成没有必要的真实dom更新。
脚手架
介绍
- xxx脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目
- 包含了所有需要的配置(语法检查、jsx编译、devServer)
- 下载好了所有相关的依赖
- 可以直接运行一个简单效果
- react提供了一个用于创建react项目的脚手架库:
create-react-app
- 项目的整体技术架构为:
react + webpack + es6 + eslint
- 使用脚手架开发的项目的特点:模块化,组件化,工程化;工程化的意思是构建工具(webpack)自动帮我们进行
语法检查,代码压缩,兼容性处理
等等一系列的功能
创建项目并启动
全局安装
1
npm install -g create-react-app
切换到想创项目的目录,使用:
1
create-react-app hello-react
进入项目文件夹:
1
cd hello-react
启动项目:
1
npm start
项目结构分析
public
public目录下存放的都是静态文件
html文件
1 |
|
robots.txt
爬虫规则文件
src
项目的源代码
App.js
App组件,也叫根组件,类似vue中的App.vue
1 | import logo from './logo.svg'; |
看以看出,App.js使用的是函数式组件,而且对于使用react脚手架开发的项目,在js文件中也可以直接使用jsx语法,也不会报错。
App.css
App组件的样式文件,在App.js文件中被导入
App.test.js
App组件的测试文件,不过使用的频率并不高
index.css
项目的全局样式文件
index.js
项目的入口文件
1 | import React from 'react'; |
在 <App />
标签外包裹<React.StrictMode>
的作用是,帮助我们自动检查代码书写不合理的地方。
而reportWebVitals
是一个函数,是用来记录页面性能的,用到了web-vitals
库
setup.Tests.js
是用来测试整个项目的
样式的模块化
React本身并没有提供像Vue那样的scoped
属性,来直接实现样式的局部作用域,那再react中如何实现组件样式的隔离呢?
如果我们在不同的组件中定义了相同的样式比如:
在Hello/Hello.css
文件中
1 | .title{ |
在Welcome/Welcome.css
文件中
1 | .title{ |
这样当我们在App.js中同时引入Hello组件和Welcome组件,就会产生样式冲突 ,那如何避免样式冲突呢?
一种解决办法就是修改Hello.css
文件名为Hello.module.css
,同时还要修改引入css文件的方式
1 | import hello from './Hello.module.css |
vscode的react插件安装

安装这个插件后,书写react代码就有对应的代码提示了
rcc:快速创建一个类式组件
1
2
3
4
5
6
7
8
9import React, { Component } from 'react'
export default class index extends Component {
render() {
return (
<div>index</div>
)
}
}rfc:快速创建一个函数式组件
功能界面的组件化编码
- 拆分组件:拆分界面,抽取组件
- 实现静态组件:使用组件实现静态页面效果
- 实现动态组件
- 动态显示初始化数据
- 数据类型
- 数据名称
- 保存在哪个组件?,即数据存放的位置
- 交互(从绑定事件监听开始)
- 动态显示初始化数据
案例todolist
body的宽度默认等于视口宽度,等于html的宽度,没有必要使用
body{width:100%}
在react组件名必须大写,否则会被认为是html标签
搭建静态结构
确定状态由谁来维护比较合适?如何把header(子组件)的数据传递给app(父组件),父组件给子组件传入一个方法,
子组件调用这个方法的时候传入值,就能修改父组件中的数据。
如何监听表单输入?添加onKeyUp属性
1
<input type="text" placeholder='请输入你的任务名称,回车键确认' onKeyUp={this.keyUp}/>
如何确定按下了enter键?(
event.keyCode
每个按键都又对应的keyCode)添加的内容不能为空,enter后要清空输入框如何拿到
<input type="checkbox"/>
的值?通过e.target.checked
item组件如何修改父组件的父组件(app组件)中的数据?先将在app组件中定义修改todos的方法,在传递给List组件,List组件直接传递给item组件。
鼠标悬浮到指定事项上,应该改变样式,可以使用
:hover
伪类实现1
2
3
4
5
6.item button{
visibility: hidden;
}
.item:hover button{
visibility: visible;
}实现删除,如何实现一个提示框提示是否删除?window.confirm,为什么不能直接写confirm
在react中如何实现vue中的计算属性?
defaultChecked属性只能生效一次,之后被删除
总数为0的时候,不能显示全选
所学知识点:
- 拆分组件、实现静态组件,注意:className、style的写法
- 动态初始化列表,如何确定将数据放在哪个组件的state中?
- 某个组件使用:放在其自身的state中
- 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
- 关于父子之间通信:
- 【父组件】给【子组件】传递数据:通过props传递
- 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
- 注意defaultChecked和checked的区别,类似的还有:defaultValue和value
- 状态在哪里,操作状态的方法就在哪里
在react中配置代理
方法一 :在package.json
中添加proxy属性
这种配置方法的缺点很明显,就是不能配置多个代理(多个目标服务器)
1 | "proxy":"http://localhost:5000" |
方法二:在src目录下新建setupProxy.js
文件,该文件中只能使用CJS语法,因为会被合并到webpack配置文件中。
这种方法的好处是能配置多个代理,并且能控制哪个请求要代理到哪个服务器;缺点是配置较为复杂,而且需要修改请求的url
1 | const proxy = require('http-proxy-middleware') |
其实前端中代理的配置,本质都差不多,都是基于http-proxy-middleware
这个库。关于代理的更多介绍,参考前端面试vue一文。
在兄弟组件间传递数据
使用消息订阅发布机制,下载pubsub库。
react路由
路由的理解
什么是路由
- 一个路由就是一个映射关系(key:value)
- key为路径, value可能是
function或component
路由分类
后端路由:
- 理解: value是function, 用来处理客户端提交的请求。
- 注册路由:
router.get(path, function(req, res))
- 工作过程: 当node接收到一个请求时, 根据请求路径,找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据。
前端路由:
- 浏览器端路由, value是component, 用于展示页面内容。
- 注册路由:
<Route path="/test" component={Test}>
- 工作过程: 当浏览器的path变为
/test
时, 当前路由组件就会变为Test组件。
react-router
- react的一个插件库
- 专门用来实现一个SPA应用
- 基于react的项目基本都会用到此库。
- 这个库其实有三个版本,分别是
web,native和anywhere
,我们学的其实是react-router-dom
,即web端的路由库。
推荐一个网站:印记中文 - 深入挖掘国外前端新领域,为国内 Web 前端开发人员提供优质文档!
单页面程序的原理就是,点击修改路由,然后路由器
监听到路由改变,替换组件。
原生html中通过a
标签来跳转页面,在react中通过Link
标签来实现,而且这个链接要使用特定的Router
标签(BrowserRouter或者HashRouter)包裹。
其实link标签最后还是会被转化成a标签,不过在此基础上添加了监听,阻止了页面跳转
再react中通过Route
标签,在组件中注册路由(注册组件的子路由),当这个组件被渲染的时候,对应的路由被注册,且启动一次路由匹配,可能触发重定向。
而在vue中,在router/index.js
文件中注册路由
1 | //BrowserRouter使用的是history路由,HashRouter代表使用的是哈希路由 |
要注意的是Link
标签,和对应的Route
标签必须在同一个BrowserRouter
标签下,为了方便起见,我们通常使用BrowserRouter
包裹整个App组件,毕竟这个应用应该只有一个路由器。
1 | //index.jsx |
如果想要点击标签有对应的高亮样式,那么就不能使用Link标签
而是NavLink标签
1 | <NavLink activeClassName="atguigu"> </NavLink> |
activeClassName
的值默认是active,但是如果你想要自定义高亮样式,那么就自定义一个类,然后传入。
封装NavLink
如果每个NavLink标签都加上activeClassName,那么这个标签的长度就变得非常长了,不方便阅读,也不够简洁,其实我们可以自定义一个MyNavLink组件,实现对NavLink的封装。本质就是返回一个添加了activeClassName
的NavLink
。
1 | import React, { Component } from 'react' |
然后复用,这样在MyNavLink标签上就不需要写activeClassName
1 | import './App.css'; |
值得注意的是,组件标签(比如NavLink,MyNavLink)
的标签体,也算是一个标签属性(children
属性),同样的,在组件标签中给children属性赋值,其实就是在书写标签体。
1 | <MyNavLink to='/about'> About </MyNavLink> |
路由组件和一般组件
根据组件的用途不同
,可分为一般组件
和路由组件
写法不同
路由组件是指切换路由展示的组件,不通过直接书的方式写来渲染;而一般组件则是直接拿来渲染的组件;比如我们有个组件About,通过上述方式使用的就叫做路由组件,如果通过直接书写即<About/>
方式展示的,就叫做一般组件。
接收到的参数不同
一般组件如果不在标签上传值,那么这个组件内部就接收不到任何值(this.props
是空对象);但是路由组件即便没有显式给它传参,也会接收到参数。
history
- go: f go(n)
- goBack: f goBack()
- goForward: f goForward()
- push: f push(path, state)
- replace: f replace(path, state)
location:
- pathname:”/about”
- search: “”
- state:undefined
match:
- params: {}
- path: “/about”
- url: “/about”
存放位置不同
一般组件通常放到components目录下,而路由组件通常放在pages或者views目录下。
Switch组件
1 | <Switch> |
如果不使用Switch组件,如果路由匹配到Test组件,还会继续匹配,最终同时会展示Home组件和Test组件;但是如果使用了Switch组件,匹配成功后,就不会继匹配了。简单的说,给Route标签包裹Switch组件的作用就是,确保只展示第一个匹配到的组件。
模糊匹配
react路由默认使用的是模糊匹配,也就是说,如果当前路由是/home/a/b
,某个组件的path是/home
,那么这个组件将会被展示,但是如果给route
标签添加exact
属性,就开启了严格匹配。
我们一般情况是不开启严格匹配的,举个例子,如果我们想要展示二级路由组件,就必须先展示一级路由组件,但是开启了严格匹配,就匹配不到一级路由组件了。
redirect
1 | //App.js |
当页面url等于localhost:3000/
,渲染的其实是App组件
,也就是根组件(因为访问localhost:3000/
会返回index.html
文件,然后解析这个html文件构建dom树,异步加载引入的js文件,当dom树构建好便执行js文件,将App组件挂载到这个html文件上),然后上述注册路由的代码就会被触发,并开始与当前页面url匹配,然后发现前2个都匹配不了,于是重定向到/about
。
嵌套路由
开启二级路由,只需要在一级路由组件中(比如About组件),继续书写NavLink
和Route
标签即可
1 | export default class About extends Component { |
必须注意的是,二级组件的匹配路径,必须以父组件(一级组件)的匹配路径开头,的比如/about
,为什么要这样写呢?因为我们想要展示出二级组件,必须先展示出一级组件,而只有按照这种方式书写二级组件匹配路径,才能做到同时匹配一级组件和二级组件。当我们点击跳转到/about
只会展示一级组件about,同时注册二级路由(因为相关注册代码就再about组件里)。如果我们再添加Redirect
,就会重定向到message组件。
1 | <div className='content'> |
携带路由参数
动态路由传参
完成动态路由传参需要三步,分别是传参,声明,和取值
1 | export default class News extends Component { |
取值:可以在Detail组件中,通过this.props.match.params
获取到我们传入的参数。
查询参数传参
完成查询参数传参需要2步,分别是传参和取值
路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
按收参数:this.props.location.search
备注:获取到的search是urlencoded
编码字符串,需要借助querystring
解析
通过state传参
通过state传参只需要2步,第一步是传参,第二部步是接收。
这种方式区别于前2种方式,路径里没有任何提示,或者说路径中不包含任何传递给路由组件的数据,
路由链接(携带参数):<Link to = {{pathname:'/demo/test', state:{name:'tom',age:18} }}>详情</Link>
感觉这个state好像history API中的history.pushState(title, state, url)
中的state。
注册路由(无需声明,正常注册即可):<Routepath="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:刷新也可以保留住参数
路由跳转模式
默认情况下,我们的路由跳转模式是push
,也就是说每次路由跳转都会往历史记录栈里push一条历史记录,其实我们可以修改路由模式为replace
,只需要在Link标签上添加replace属性。
其实在vue中也是一样的Router.push()
对应的就是push模式,Router.replace
对应的就是replace模式
编程式路由导航
this.props.hsitory
属性下有2个方法:push和replace,分别对应2中路由跳转模式。
1 | history: |
1 | <ul> |
withRouter
一般组件并不会被传递路由组件的那些api(比如go,goBack,goForward),不显式地给一般组件传参,一般组件就接收不到任何参数,为了能让一般组件也能使用路由组件的那些api,我们就需要借助withRouter
这个函数
1 | import { withRouter } from 'react-router-dom' |
在新版本的react-router中,这个api已经被移除了。
BrowserRouter和HashRouter的区别
BrowserRouter使用的是History API,借助这些api来形成历史记录,而HashRouter单纯是通过修改URL的哈希部分,来形成历史记录的
state传参中的state参数,是history api特有的,使用BrowserRouter并刷新页面,state不会丢失,而使用HashRouter并刷新页面,state会丢失
1
history.pushState(state, title, url)
UI组件库Ant Design
- 使用ui组件库不需要去背,用熟练了就好
- 难点在于分析那些代码是属于你想要的组件的
- 按需引入样式,减少最终文件的体积,查看文档(具体位置是《在create-react-app中使用》)按照文档的指示一步一步操作即可。
- 修改主题颜色
redux
学习文档
英文文档:https://redux.js.org/
中文文档:http://www.redux.org.cn/
Github:https://github.com/reactjs/redux
redux是什么
redux是一个专门用于做
状态管理
的JS库(不是react插件库)。它可以用在react,angular, vue等项目中,但基本与 react 配合使用。
作用:集中式管理react应用中多个组件共享的状态
什么情况下需要使用redux
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用,如果不用比较吃力才考虑使用。

reducer
的作用不仅仅包括更新数据,还有初始化数据的作用,因为刚开始是没有previewState
的,所以它的值是undefined
使用步骤
定义一个reducer函数(根据当前想要做的修改返回一个新的状态),reducer函数接收2个参数,第一个参数是previosState,第二个参数是actions,reducer根据previosState和actions来返回新的状态。
使用createStore方法,传入reducer函数生成一个store实例对象
使用store实例的
subscribe
方法,订阅数据的变化(数据一旦变化,可以得到通知)使用store实例的
dispatch
方法提交action
对象,触发数据变化(告诉reducer你想怎么改数据)使用store实例的
getState
方法获,取最新的状态数据更新到视图中
在react中使用redux
安装所需包:
@reduxjs/toolkit
这个包用来简化redux代码,react-redux
是react开发团队在redux基础上开发的,更方便react开发的redux包,注意,不需要下载redux。1
npm install @reduxjs/toolkit react-redux
在src目录下新建store文件夹,在store文件夹下新建
modules
目录和index.js
文件,再在modules
目录下新建countStore.js
文件,使用createSlice方法创建一个子仓库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//`countStore.js`
import {createSlice} from '@reduxjs/toolkit'
//返回创建的子仓库对象
const counStore = createSlice({
name:'counter',
initialState:{
count:0
},
//修改状态的方法,支持直接修改
reducers:{
increment:(state)=>{
//直接修改
state.count++
},
decrement:(state)=>{
state.count--
}
}
})
//解构出ActionCreator函数
//为什么ActionCreator和reducers中的方法同名?
//后续调用dispatch(increment())的时候,就等同于调用increment方法
const {increment,decrement} = counStore.actions
const reducer = counStore.reducer
//导出这个store的Action Creators,供后续dispatch方法使用
export {increment,decrement}
//默认导出这个store的reducer,注意导出的不是子仓库实例,而是它的reducer
export default reducer使用
configureStore
配置所有子仓库,创建一个根仓库实例并导出,不得不说实在是太像Vuex了。1
2
3
4
5
6
7
8
9
10
11
12
13//index.js
import {configureStore} from '@reduxjs/toolkit'
//获取 counStore子仓库的reducer
import countReducer from './modules/countStore'
//创建根store
const store = configureStore({
reducer:{
countReducer
}
})
//导出一个根store
export default store将
redux
注入到react
,就需要借助react-redux
这个插件了。从
react-redux
中导入Provider
组件,这个组件包裹的所有子组件,内部都能使用redux
中的数据,还要给Provider
组件的store
属性注入根仓库实例,完成react和redux的结合。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//App.jsx
//从`react-redux`中引入Provider组件
import { Provider } from 'react-redux'
//引入根store
import store from './store/index'
//映入Redux组件
import Redux from './components/reduxlearning'
function App() {
return (
//注入到react,后续在Redux组件中就能使用redux中的数据了
<Provider store={store}>
<div className="App">
<Redux></Redux>
</div>
</Provider>
);
}在Redux组件中使用redux也需要导入
react-redux
插件中的方法,常见的方法包括useSelector
:用来获取某个子仓库中的状态,useDispatch
用来分发同步或者异步actions。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import React from 'react'
import {useSelector, useDispatch} from 'react-redux'
import { increment,incrementByX } from '../../store/modules/countStore'
export default function Redux() {
const {count} = useSelector(store=>store.countReducer)
const dispatch = useDispatch()
return (
<div>
{count}
{/* increment是一个Action Creator,调用后会返回一个Action,dispatch这个action表示调用increment方法 */}
{/* 调用Creator的时候可以传入一个参数,表示给对应的方法传值 */}
<button onClick={()=>{dispatch(increment())}}>+</button>
<button onClick={()=>{dispatch(incrementByX(10))}}>+10</button>
</div>
)
}在redux中不但存在同步action,还存在异步actions
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// src\store\modules\takeaway.js
import {createSlice} from '@reduxjs/toolkit'
const foods = createSlice({
name:'foods',//必须属性,用于区别其他子仓库
initialState:{
foodList:[]
},
reducers:{
setFoodList(state,action){
state.foodList = action.payload
}
}
})
// 得到同步actions
const {setFoodList} = foods.actions
// 定义一个异步action creator
// 调用异步action creator 返回一个函数,而不是一个对象
// 返回的函数会自动被传入一个参数dispatch,用来调用同步action
const getFoodList = ()=>{
return async (dispatch)=>{
const res = await fetch("http://localhost:3004/takeaway")
const data = await res.json()
dispatch(setFoodList(data))
}
}
// 导出同步和异步actions
export {setFoodList,getFoodList}
// 导出子仓库的reducer,用于注册。配置
export default foods.reducer1
2
3
4
5
6
7
8
9// src\store\index.js
import { configureStore } from '@reduxjs/toolkit'
import foods from './modules/takeaway'
const store = configureStore({
reducer:{
foods
}
})
export default store1
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// src/App.js
import NavBar from './components/NavBar'
import Menu from './components/Menu'
import Cart from './components/Cart'
import FoodsCategory from './components/FoodsCategory'
import './App.scss'
//导入异步actions
import { getFoodsList } from './store/modules/takeaway'
import { useDispatch,useSelector } from 'react-redux'
import { useEffect } from 'react'
const App = () => {
//只能在组件顶级作用域使用hooks
const dispatch = useDispatch()
//在组件挂载的时候,执行一次dispatch,调用异步Action
//[dispatch] 是依赖数组,表示只有当 dispatch 发生变化时才会重新运行这个 useEffect
//实际上,dispatch 在组件的生命周期内通常是稳定的,所以这个 useEffect 只会在组件挂载时运行一次
useEffect(()=>{
dispatch(getFoodsList())
},[dispatch])
// 从redux中取出foods仓库中的状态
// React-Redux 的 useSelector 会自动订阅 Redux store 的变化。
// 当 foodsList 更新时,useSelector 会重新运行,触发组件重新渲染,拿到最新的数据
// 当 dispatch(getFoodsList()) 完成后,Redux store 中的 foodsList 被更新。
// useSelector 检测到状态变化,重新提取最新的 foodsList。
// 组件会自动重新渲染,展示最新的数据。
let {foodsList} = useSelector(store=>store.foods)
return (
<div className="home">
{/* 导航 */}
<NavBar />
{/* 内容 */}
<div className="content-wrap">
<div className="content">
<Menu />
<div className="list-content">
<div className="goods-list">
{/* 外卖商品列表 */}
{foodsList.map(item => {
return (
<FoodsCategory
key={item.tag}
// 列表标题
name={item.name}
// 列表商品
foods={item.foods}
/>
)
})}
</div>
</div>
</div>
</div>
{/* 购物车 */}
<Cart />
</div>
)
}
export default AppuseSelector补充:
- useSelector用来从redux中取出特定子仓库中的状态
- useSelector 会自动订阅 Redux store 的变化。在上述例子中,当
dispatch(getFoodsList())
完成后,Redux store 中的foodsList
被更新。useSelector 检测到状态变化,重新提取最新的 foodsList。组件会自动重新渲染,展示最新的数据。
useDispatch补充:
useDispatch
用来分发同步或者异步actions。调用useDispatch
返回dispatch方法dispatch(actioncreator(params,params2,...))
:我们在分发action的时候,还可以传入参数,可以在对应的reducers的第二个参数接收到返回值:
const res = dispatch(setToken(values))
如果
setToken
是一个普通的同步action creator
,dispatch会立即返回一个普通的 action 对象,此时 await 是无效的。如果
setToken
是一个异步 action creator,dispatch会立即返回 调用setToken
返回的async
函数的返回值,也就是一个Promise
对象返回的Promise
的状态会在async
函数的返回值确定后确定这意为着在分发异步actions的情况,我们可以对
dispatch
使用await
,从而确保某些代码能在异步操作完成后再执行。
react-redux
- 所有的UI组件都应该包裹一个容器组件,他们是父子关系。
- 容器组件是真正和redux打交道的,里面可以随意的使用redux的api。
- UI组件中不能使用任何redux的api。
- 容器组件会传给UI组件:(1).redux中所保存的状态。(2).用于操作状态的方法。
- 备注:容器给UI传递:状态、操作状态的方法,均通过props传递。

react扩展
setState
setState(stateChange, [callback])
—— 对象式的 setState
stateChange
为状态改变对象(该对象可以体现出状态的更改)callback
是可选的回调函数,它在数据和视图都更新完毕后 (render
调用后) 才被调用
setState(updater, [callback])
—— 函数式的 setState
updater
为返回stateChange
对象的函数。updater
可以接收到state
和props
(第一个参数是state,第二个参数是props)callback
是可选的回调函数,它在数据和视图都更新完毕后 (render
调用后) 才被调用- 这种情形,传入的2个参数都是函数
总结:
- 对象式的
setState
是函数式的setState
的简写方式(语法糖)。 - 使用原则:
- 如果新状态不依赖于原状态,比如
this.setState({count:90})
使用对象方式 - 如果新状态依赖于原状态,使用函数方式
- 如果需要在
setState()
执行后获取最新的状态数据, 要在第二个callback
函数中读取
- 如果新状态不依赖于原状态,比如
懒加载
- 从react中引入lazy函数,修改导入路由组件的方式(竟然不是从
react-router
中导入的吗) - 导入Suspense组件包括Route,传入组件尚未被加载的时候,展示的结构或者组件
1 | import React, { Component,lazy,Suspense } from 'react' |
1 | <Suspense fallback={<h3>loading</h3>}> |
Hooks
React.useState
这个钩子的作用就是,让函数式组件也可以有自己的状态,并且可以对状态进行读写。
每次修改状态,都会重新调用函数式组件,但是由于对useState
特殊处理,并不会重新初始化变量。
1 | import React from "react" |
React.useEffect
Effect Hook 可以让你在函数组件中创建不是由事件引起,而是由渲染本身引起的操作,用来模拟类式组件中的生命周期函数
React 中的副作用操作:
- 发 ajax 请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实 DOM
语法和说明:
1 | useEffect(() => { |
1 | React.useEffect(()=>{ |
可以把 useEffect Hook
看做如下三个函数的组合:
componentDidMount()
:如果第二个参数传入的是空数组,表示不监听任何状态改变,则效果就相当于componentDidMount()
componentWillUnmount()
:在传入的回调函数中,返回的函数,其作用就相当于这个钩子componentDidUpdate()
:如果第二个参数传入的不是空数组,监听的状态改变后,也会触发传入的回调函数。- 如果不传入第二个参数,则代表监听所有状态改变,或者说只要组件更新了,就调用传入的回调函数
可以看出在函数式组件中,使用useEffect
来模仿类式组件中的生命周期钩子,还是比较麻烦的。
React.useRef
RefHook可以在函数组件中,存储/查找组件内的标签或任意其它数据
语法:const refContainer = React.useRef()
作用:保存标签对象,功能与React.createRef()
一样
1 | import React from "react" |
React.Fragments
1 | import { Fragment } from 'react' |
用来解决react组件中只能有一个根标签的问题,Fragment标签不会被渲染,就类似vue3中的fragment或者template
React.createContext
这个api,主要用来解决祖先组件给后代组件传值,就类似vue中的provide
和inject
- 使用
createContext
创建一个上下文对象context
,这个对象应该放到所有后代组件都能访问到的位置 - 使用
context.Provider
包裹第一代后代组件 - 在
Provider
组件上添加value属性,给后代组件传值
1 | import React, { Component } from 'react' |
如果是函数式组件,接收祖先组件Provide的值,只能使用useContext API
,传入先前创建好的上下文对象
1 | import React, { createContext, useContext } from 'react' |
Hooks使用规则
- 只能在组件中或者其他自定义Hook函数中调用
- 只能在组件的顶层调用,不能嵌套在if、for、其他函数中
下面的情况都是不被允许的:
1 | import React from 'react' |
1 | import React from 'react' |
自定义Hooks
1 | import React, { useState } from 'react' |
组件优化
Component的2个问题
只要执行setState()
,即使不改变状态数据,组件也会重新render()
,效率低
只要当前组件重新render()
,就会自动重新render子组件,纵使子组件没有用到父组件的任何数据,效率低
1 | //父组件 |
1 | //子组件 |
效率高的做法:只有当组件的state或者props数据发生改变时,才重新render()
原因:Component中的shouldComponentUpdate()
总是返回true
解决:
重写
shouldComponentUpdate()
方法:比较新旧state或props数据,如果有变化才返回true
,如果没有返回false
shouldComponentUpdate()
接收两个参数:nextProps
: 下一次的props
。nextState
: 下一次的state
。
需要在这个方法中比较当前的
props
和state
(通过this.props
和this.state
)与下一次的props
和state
。如果返回true
,组件会重新渲染;如果返回false
,组件不会重新渲染。问题是如何进行对象之间的比较呢?直接使用
this.props === nextProps
这种方式吗?我测试过,即便父组件给子组件传入的只是一个常量,父组件触发render之后,子组件的nextProps
也不等于this.props
,虽然父组件传入的props确实没有变化,但是地址不同,所以不能使用===
的方式。使用
PureComponent
:PureComponent
重写了shouldComponentUpdate()
,只有state或props数据有变化才返回true
注意:
- PureComponent只是进行state和props数据的浅比较,本质上就是只比较对象的第一层级属性值。对于基本数据类型(如字符串、数字、布尔值等),它直接比较这些值是否相等;而对于引用数据类型(如对象和数组),则比较它们的引用地址是否相同。
- 不要直接修改state数据,而是要产生新数据,否则视图不会更新。
项目中一般使用PureComponent来优化上述问题
render Props
1 | import React, { Component } from 'react' |
上述例子中,A和B的父子组件关系是显而易见的,因为我们直接把<B></B>
写在了A组件中,此时如果A组件想要给B组件传值,直接在B组件标签上添加属性即可。
但是除此以外,让A,B组件形成父子关系的方式还有如下方式:
1 | import React, { Component } from 'react' |
此时想要在A组件中展示B组件,还需要添加代码:
1 | class A extends Component { |
其中this.props.children
的值是一个对象,结构较为复杂。
此时想要实现A组件向B组件传值,貌似就难以实现了,反而,实现Parent组件向B组件传值变得简单了。
其实,要实现A,B组件的父子关系,还可以通过另一中方法实现:
1 | import React, { Component } from 'react' |
然后只需要修改A组件代码:
1 | class A extends Component { |
就能实现A,B组件的父子关系,这种方法还有一个好处就是,能实现A组件给B组件传递参数
1 | import React, { Component } from 'react' |
我们可以发现,这一点其实很像vue中的插槽,A,B组件,在我们未使用它们之前,它们并没有明确的父子组件关系,但是我们可以借助render props
实现给组件动态传递结构。
error boundary
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:只能捕获后代组件生命周期中(比如render中)产生的错误,不能捕获组件自己产生的错误,和其他组件在合成事件、定时器中产生的错误。
使用方式:getDerivedStateFromError
配合componentDidCatch
1 | import React, { Component } from 'react' |
组件通信总结
组件间的关系
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式
- props
- children props
- render props
- 消息订阅-发布
pubs-sub、event等等 - 集中式管理:
redux、dva等等 - ConText:生产者-消费者模式
比较好的搭配方式
父子组件:父组件通过props向子组件传参,子组件调用props中的方法向父组件传参
兄弟组件:状态提升,消息订阅-发布、集中式管理

祖孙组件(跨级组件):消息订阅-发布、集中式管理、context(开发用的少,封装插件用的多)
React Router6
概述
- React Router 以三个不同的包发布到 npm 上,它们分别为:
- react-router: 路由的核心库,提供了很多的:组件、钩子。
- react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如
<BrowserRouter>
等。 - react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如
<NativeRouter>
等。
- 与React Router 5.x 版本相比,改变了什么?
- 内置组件的变化:移除
<Switch/>
,新增<Routes/>
等。 - 语法的变化:
component={<About />}
变为element={<About />}
等。 - 新增多个hook:useParams、useNavigate、useMatch 等。
- 官方明确推荐函数式组件了!!!
- 内置组件的变化:移除
变化
Routes与Route
移除
<Switch/>
,新增<Routes/>
,而且是必须使用<Routes/>
包裹<Route/>
,默认匹配到了组件就不会继续匹配。使用Route注册组件的语法也改变了:
1
2<Route path='/about' component={About}></Route> //旧版
<Route path='/about' element={<About/>}></Route> //新版而且后面使用Route来注册子路由的方式也不推荐使用了,转为使用路由表。
Navigate
删除了Redirect,添加了Navigate,只要 <Navigate>
组件被渲染,就会修改路径,切换视图。
replace
属性用于控制跳转模式(push 或 replace,默认是 push)。
1 | import React, { useState } from 'react'; |
<Navigate>
组件的作用是只要被渲染就会修改路径,切换视图。replace
属性用于控制跳转模式,默认是push
。示例代码展示了如何根据
sum
的值来决定是否使用<Navigate>
组件进行路径切换。如果
<Navigate to='/about' />
始终存在于组件中(即没有通过条件判断来控制它的渲染),那么无论用户点击哪个<NavLink>
,页面都会被强制跳转回/about
,最好的做法应该是写在路由表中:1
2
3
4{
path:'/',
element: <Navigate to='/about'></Navigate>
}
NavLink
在react router6中,activeClassName已经被废弃,下面是新的语法介绍:
1 | <NavLink className={computedClassName} to="/about">About</NavLink> |
1 | function computedClassName({isActive}){ |
useRoutes
在React Router6中,貌似想要使用嵌套路由,就必须使用路由表,即借助useRoutes
1 | // routes/index.jsx |
1 | //App.js |
1 | // pages/about/index.jsx |
RouterProvider
1 | // router/index.jsx |
导出的router对象上还挂载了navigate
方法,以便再非组件环境实现路由跳转
1 | router.navigate('/login') |
1 | // index.js |
1 | import { Provider } from 'react-redux' |
- 使用
RouterProvider
的方式,就不能使用<BrowserRouter>
,因为二者都能创建一个路由上下文,不能同时使用 - 一级路由组件会替换
RouterProvider
标签,会自动拥有路由上下文对象,所以只能在一级路由组件中(或者它的子组件)使用Link标签。 - 使用这种方式,也需要创建路由表,不过最终是传入
createBrowserRouter
创建一个router
对象,然后注入RouterProvider
RouterProvider
会成为一级路由组件的出口,App组件想要被展示,想要书写Link标签,也需要在路由表中配置,配置为一级路由。
useParams
被用来在函数式组件中,获取传递过来的动态路由参数。
调用这个函数,不需要传入参数,会返回一个动态路由参数对象
1 | //about/news/detail/index.jsx |
useSearchParams
被用来在函数式组件中,获取传递过来的查询参数。
调用这个函数,也不需要传入任何参数,返一个数组;第一个参数是一个URLSearchParams
对象,只能调用这个对象的get方法取得对应的值;第二个参数是一个函数,用来修改当前页面的查询参数,用的不多,会触发组件的重新渲染。
1 | import React from 'react' |
由此可以看出useSearchParams
和useParams
使用起来都是非常简单的,直接调用即可,不需要传入任何参数,不过具体的使用方法还是略有差别。
useMatch
useLocation
调用这个函数,不需要传入值,返回一个location
对象,这个对象的结构,和类式路由组件中的this.props.location
的结构是相同的。
我们就可以通过这个函数,来接受传入函数式路由组件中的state。
1 | <div className='header'> |
1 | import React from 'react' |
useNavigate
无论是普通组件还是路由组件,都可以使用这个函数实现编程式导航。
直接调用useNavigate
,返回一个navigate
函数。
navigate(to, options)
接受两个参数:
- **
to
**:- 类型:
string
或object
- 表示要导航的目标路径。
- 如果是字符串,则表示路径(如
/about
)。 - 如果是对象,可以包含以下属性:
pathname
: 目标路径(如/about
)。search
: 查询参数(如?id=123
)。hash
: 锚点(如#section1
)
- 类型:
- **
options
**:- 类型:
object
- 可选配置项:
replace: true
:替换当前的历史记录条目,而不是添加一个新的条目。state
:传递状态数据(与to
对象中的state
等效)。
- 类型:
useNavigate
还支持基于相对路径的导航。例如,你可以通过传递一个负数来返回上一页。
1 | const navigate = useNavigate(); |
其他
useInRouterContext:用来判断一个组件是否在路由环境中,简单的来说,是否被
BrowserRouter
或者HashRouter
包裹。useNavigationType:返回当前的导航类型(用户是如何来到当前页面的)。返回值:POP、PUSH、REPLACE。POP是指在浏览器中直接打开了这个路由组件(刷新页面)。
useOutLet:用来呈现当前组件中渲染的嵌套路由
1
2
3
4const result = useOutlet()
console.log(result)
//如果嵌套的路由组件没有挂载,则result为null
//如果嵌套路由已经挂载,则展示嵌套的路由对象useResolvedPath:用来解析路径,传入任意一个路径,返回一个解析后的对象。
React hooks总结
基础hooks
useState
:用来理组件的局部状态1
const [count, setCount] = useState(0);
在上述例子中,通过setCount修改count还会出发组件更新
useEffect
:用来处理副作用,在函数式组件中可以用来模拟componentDidMount
,componentDidUnmount
,componentDidUpdate
这三个生命周期函数
组件传值
createContext
:创建一个上下文对象context,这个上下文对象上有一个Provider属性,返回Provider组件,用来包裹子组件,然后通过给这个Provider组件value
属性赋值(可以是基本数据类型,也可以是一个对象),来给后代组件传值。useContext
:传入一个上下文对象,在返回值中拿到祖先组件传递过来的值。
捕获DOM
useRef
:用来捕获DOM元素,替代createRef
,必须通过ref.current
来捕获访问到的DOM元素。1
2
3
4
5
6
7
8
9
10import React from "react"
export default function useState(){
const target = React.useRef()
return (
<div>
<input type="text" ref={target} />
<button onClick={()=>{alert(target.current.value)}}>点击提示</button>
</div>
)
}
路由相关
useNavigate
:直接调用useNavigate
,返回一个navigate
函数,用来实现编程式导航useParams
:直接调用后,会返回一个动态路由参数对象,用来获取通过动态路由传参,传递过来的参数:1
2
3
4
5
6
7
8
9import React from 'react'
import { useParams } from 'react-router-dom'
export default function Detail(){
const params = useParams()
return (
<div>Detail:{params.id}</div>
)
}useSearchParams
:直接调用后,返回一个数组,通过数组解构,在第一个元素就能拿到查询参数对象,不过需要调用get
方法来取值。1
2
3
4
5
6
7
8
9
10
11import React from 'react'
import { useSearchParams } from 'react-router-dom'
export default function Message(){
const [search, setSearchParams] = useSearchParams()
return (
<div>
Message:{search.get('name')}
<button onClick={()=>{setSearchParams('name=cindy')}}>点击修改查询参数</button>
</div>
)
}useLocation
:调用这个函数,不需要传入值,返回一个location
对象,从这个对象上我们就能直接拿到通过路由传递过来的state
参数。1
2
3
4
5<div className='header'>
//传递state
<NavLink to='message?name=tom&age=21' state={{name:'sun'}}> message </NavLink>
<NavLink to='news'>news</NavLink>
</div>1
2
3
4
5
6
7
8
9
10
11
12
13import React from 'react'
import { useSearchParams,useLocation } from 'react-router-dom'
export default function Message(){
console.log('render')
const [search, setSearchParams] = useSearchParams()
console.log(useLocation().state)//输出{name: 'sun'}
return (
<div>
Message:{search.get('name')}
<button onClick={()=>{setSearchParams('name=cindy')}}>点击修改查询参数</button>
</div>
)
}
redux相关
useDispatch
:用来分发actionsuseSelector
:用来从指定子仓库中获取状态