文章目录
组件的生命周期(类组件才有)
概念:
- 生命周期:组件从被创建到挂载到页面中运行,再到组件不用时被卸载的过程。
- 钩子函数:生命周期各个阶段伴随的被调用的方法。提供了某个阶段的重要时机。
创建时(挂载阶段):
- 时机:页面刚加载完时(组件创建时)
- 钩子函数执行顺序:constructor() => render() => componentDidMount.
注意: render()方法里不能调用setState(),因为setState()会引发组件渲染,组件渲染又会触发render(),render()里又调用setState(),如此往复,循环不止。
示范代码:
//导入react
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'
class App extends React.Component{
constructor(props){
super(props)
console.log("constructor")
//初始化state
this.state={
count:0
}
}
render(){
console.log("render")
//渲染
return (
<div>
<h1 id="title">统计豆豆被打次数:</h1>
<button id="btn">打豆豆</button>
</div>
)
}
componentDidMount(){
console.log("componentDidMount")
//等待组件完成之后进行DOM操作,或发送ajax请求
const title=document.getElementById("title")
console.log(title)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
结果:
更新时:
- 时机:会导致组件更新的三种情况:props的更新,setState()的调用,forceUpdate()的使用。
- 钩子函数执行顺序:render() => componentDidUpdate().
注意: componentDidUpdate()方法若要调用setState()方法,必须放在一个能使循环终止的if条件里,若没有终止条件,则setState()会引发组件渲染,组件渲染又会触发render(),render()渲染完后执行componentDidUpdate(),调用setState(),如此往复,循环不止。
prevProps:常用prevProps与props比较来判断是否终止,prevProps就是上一个props,其用法如下:
componentDidUpdate(prevProps){//作为参数传入
console.log("prevProps.count")//像props一样调用里面的值
}
触发时机示范代码:
//导入react
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'
class App extends React.Component{
constructor(props){
super(props)
console.log("constructor")
//初始化state
this.state={
count:0
}
}
pushBtn=()=>{
//setState(),触发更新
this.setState({
count:this.state.count+1
})
//forceUpdate(),触发更新
//this.forceUpdate()
}
render(){
console.log("render")
return (
//props传值,触发更新
<div>
<Counter count={this.state.count}/>
<button id="btn" onClick={this.pushBtn}>打豆豆</button>
</div>
)
}
componentDidUpdate(){
console.log("componentDidUpdate")
}
}
class Counter extends React.Component{
render(){
console.log("render2")
return (
<div>
<h1 id="title">统计豆豆被打次数:{this.props.count}</h1>
</div>
)
}
//渲染完后获取DOM
componentDidUpdate(){
console.log("componentDidUpdate2")
const title=document.getElementById("title")
console.log(title)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
结果:
结果解释:
点击按钮调用了setState(),App组件更新渲染,输出render。state值更新后,props更新,引发Counter组件更新渲染,输出render2。
卸载时:
- 时机:组件从页面中消失。
触发时机示范代码:
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'
class App extends React.Component{
constructor(props){
super(props)
console.log("constructor")
//初始化state
this.state={
count:0
}
}
pushBtn=()=>{
this.setState({
count:this.state.count+1
})
}
render(){
console.log("render")
return (
<div>
{
this.state.count>3
? <p>豆豆被打死了</p>
:<Counter count={this.state.count}/>
}
<button id="btn" onClick={this.pushBtn}>打豆豆</button>
</div>
)
}
}
class Counter extends React.Component{
render(){
console.log("render2")
return (
<div>
<h1 id="title">统计豆豆被打次数:{this.props.count}</h1>
</div>
)
}
componentWillUnmount(){
console.log("该组件已被卸载")//组件消失时输出
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
不常用钩子函数介绍(不重要,不好用):
React组件复用:
- 复用概念:两个组件出现了相似的可以合并的功能,那我们倾向于将这个功能封装在一个组件中,当需要使用该功能,就调用该组件,复用相似的功能。
- 复用什么?:1.state ; 2.操作state的方法。
- 怎么复用?:1.render props模式 ; 2.高阶组件。
render-props模式
思路:
- 怎么复用state?:组件在使用功能组件时,添加一个值为函数的props,通过该函数的参数获取state,这样,功能组件调用props函数时,state就被传入了该组件(使用功能组件的组件)内。
- 怎么渲染任意的UI?:使用props函数的返回值作为要渲染的UI内容,因为该函数在组件(使用功能组件的组件)内,渲染什么样的UI就是由该组件决定的了。
- 优化:可用children代替prop传函数,这样更加直观
代码实例:
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'
//图片必须这样导入,且放在src内
import img from './imgs/logo192.png'
class Mouse extends React.Component{
//state,数据
state={
x:0,
y:0
}
//功能:得到鼠标位置坐标
componentDidMount(){
window.addEventListener('mousemove',this.changePosition)
}
//处理state的方法,更新鼠标位置坐标
changePosition=e=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
render(){//传参 (???)
return this.props.render(this.state)
//children优化:this.props.children(this.state)
}
}
class App extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div>
<h1>render props模式</h1>
<Mouse render={(mouse)=><p>
鼠标位置:{mouse.x},{mouse.y}
</p> }/>
<Mouse render={(mouse)=>
<img src={img} alt='react' style={{
position:'absolute',
top:mouse.y-96,
left:mouse.x-96
}} ></img>
}/>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
/*children优化
<Mouse>{
(mouse)=><p>
鼠标位置:{mouse.x},{mouse.y}
</p>
}</Mouse>
<Mouse>{
(mouse)=><img src={img} alt='react' style={{
position:'absolute',
top:mouse.y-96,
left:mouse.x-96
}} ></img>
}</Mouse>
*/
代码优化:
- 给render props或children添加props校验
- 应该在组件卸载时删除mousemove事件绑定
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'
//图片必须这样导入,且放在src内
import img from './imgs/logo192.png'
class Mouse extends React.Component{
//state,数据
state={
x:0,
y:0
}
//功能:得到鼠标位置坐标
componentDidMount(){
window.addEventListener('mousemove',this.changePosition)
}
//处理state的方法,更新鼠标位置坐标
changePosition=e=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
render(){//传参 (???)
return this.props.render(this.state)
//children优化:this.props.children(this.state)
}
//移除事件绑定优化
componentWillUnmount(){
window.removeEventListener('mousemove',this.changePosition)
}
}
//校验优化
Mouse.propTypes={
render:PropTypes.func.isRequired
//children:PropTypes.func.isRequired
}
class App extends React.Component{
render(){
return (
<div>
<h1>render props模式</h1>
<Mouse render={(mouse)=><p>
鼠标位置:{mouse.x},{mouse.y}
</p> }/>
<Mouse render={(mouse)=>
<img src={img} alt='react' style={{
position:'absolute',
top:mouse.y-96,
left:mouse.x-96
}} ></img>
}/>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
/*children优化
<Mouse>{
(mouse)=><p>
鼠标位置:{mouse.x},{mouse.y}
</p>
}</Mouse>
<Mouse>{
(mouse)=><img src={img} alt='react' style={{
position:'absolute',
top:mouse.y-96,
left:mouse.x-96
}} ></img>
}</Mouse>
*/
高阶组件(复用)
概念:
接受React组件作为输入,输出一个新的React组件的函数。
更通俗地描述为,高阶组件通过包裹(wrapped)被传入的React组件,经过一系列处理,最终返回一个相对增强(enhanced)的React组件,供其他组件调用。
使用步骤:
- 1.创建一个高阶组件(函数),组件作为参数
- 2.函数内再搞一个类组件,与render props类似
- 3.在类组件的render里返回增强后的组件(在这里其实就是用props多传了个state)
- 4.函数返回Mouse组件
- 5.再将渲染组件封装,调用
细节:
- 设置displayName便于区分
- props传递
代码实例:
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'
//图片必须这样导入,且放在src内
import img from './imgs/logo192.png'
//1.创建一个高阶组件(函数),组件作为参数
function withMouse(WrappedComponent){
//2.函数内再搞一个类组件,与render props类似
class Mouse extends React.Component{
//state,数据
state={
x:0,
y:0
}
//得到鼠标位置坐标
componentDidMount(){
window.addEventListener('mousemove',this.changePosition)
}
changePosition=e=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
//组件卸载后,移除事件绑定
componentWillUnmount(){
window.removeEventListener('mousemove',this.changePosition)
}
//3.在render里返回增强后的组件(在这里其实就是用props多传了个state)
render(){//再传个props到WrappedComponent中
return <WrappedComponent {...this.state}{...this.props}/>
//children优化:this.props.children(this.state)
}
}
/*设置displayName便于区分
Mouse.displayName='withMouse${getName(WrappedComponent)}'*/
//4.函数返回Mouse组件,返回值其实就是Mouse组件包着带props的WrappedComponent组件
return Mouse
}
/*设置displayName便于区分
function getName(WrappedComponent){
return WrappedComponent.displayName||WrappedComponent.name||'Component'
}*/
//5.再将渲染组件封装
const Position=props=>{
return (
<div>
<p>
鼠标位置:{props.x},{props.y}
</p>
<img src={img} alt='react' style={{
position:'absolute',
top:props.y-96,
left:props.x-96
}} ></img></div>
)
}
//增强后的组件
const MousePosition=withMouse(Position)
class App extends React.Component{
render(){
return (
<div>
<h1>render props模式</h1>
<MousePosition></MousePosition>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
React原理揭秘
setState()方法深入
setState()的异步现象(在react的 合成事件与钩子函数中)
在react的 合成事件与钩子函数中,用setState()更新状态后状态不会马上更新,这导致两种现象:
- 钩子函数内获取改变的状态得到的还是改变前的值。
- 钩子函数中多次调用setState()也不会相互影响(呈覆盖关系)。
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = { count:0 }
}
increase=()=>{
this.setState({count:this.state.count+1});//this.state.count=>1
console.log(this.state.count);//输出0
this.setState({count:this.state.count+1});//this.state.count=>1
console.log(this.state.count);//输出0
}
render() {
return (
<div>
<div>setState</div>
<button onClick={this.increase}>btn</button>//点击一下count的值为1
</div>
);
}
}
setState()的异步原理
setState本身的执行过程是同步的,只是因为react的合成事件与钩子函数的执行顺序在更新之前,所以不能直接拿到更新后的值,形成了所谓的异步。
钩子函数中所有的setState 会统一的更新,而且,当 state 中的属性名称相同时,后一次的 setState 会把上一次队列中的覆盖掉。
详细原理
setState()方法的进阶使用
1.setState((state,props)=>{})语法:
- 参数state:表示最新的state
- 参数props:表示最新的props
- 效果:使钩子函数中多次调用setState()可以相互影响
increase=()=>{
this.setState((state,props)=>{count:this.state.count+1});//this.state.count=>1
console.log(this.state.count);//输出0
this.setState((state,props)=>{
console.log(this.state.count)//输出1
return{count:this.state.count+1}//this.state.count=>2
});
console.log(this.state.count);//输出0
}
2.setState()的第二个参数:
- 语法:setState(updater,callback)
- 效果:状态更新(页面,DOM都重新渲染)后立即执行某个操作
increase=()=>{
this.setState(
(state,props)=>{
return{count:this.state.count+1}//this.state.count=>1
},
()=>{
console.log('状态更新完成:',this.state.count)//输出1
console.log(document.getElementById('title').innerText)//输出 计数器: 1 与DOM渲染更新后一致
}
);
console.log(this.state.count);//输出0
}
render(){
return (
<h1 id="title">计数器:{this.state.count}</h1>//输出 计数器: 1
)
}
JSX语法深入
- JSX仅仅只是createElement()方法的语法糖(简化语法)
- JSX语法会被@babel/preset-react插件编译为createElement()方法
- React元素:是一个对象,用来描述你希望在屏幕上看到的内容
怎么看到React元素?
const element = (
<h1>
Hello JSX
</h1>
);
console.log(element)
组件更新机制
- 父组件更新后,其子组件必更新
- 父组件中的某一子组件重新渲染时,只有该子组件的当前组件子树(当前组件及其所有子组件)会重新渲染
组件性能优化
减轻state
- 只储存跟组件渲染相关的数据
- 其他的需要在多个方法中使用的数据,如定时器id,应放入this中
class App extends React.Component{
componentDidMount(){
this.timerId=setInterval(()=>{},2000)
}
componentWillUnmount(){
clearInterval(this.timerId)
}
renser(){...}
}
避免不必要的渲染
- 问题:父组件渲染会导致没有变化的子组件也重新渲染,造成不必要的渲染
- 解决方式:使用钩子函数shouldComponentUpdate(nextProps,nextState)
- 用法:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
- 触发时机:更新阶段的钩子函数,组件重新渲染前执行,shouldComponentUpdate => render()
示范代码:
shouldComponentUpdate(nextProps,nextState){
//props同理
console.log(nextState)//最新状态
console.log(this.state)//更新前状态
if(nextState.number===this.state.number)return false
else return true
}
纯组件
- React.PureComponent与React.Component功能相似
- 区别:PureComponent内部自动实现shouldComponentUpdate,不需要手动比较
- 注意点:因为纯组件内部的对比是浅层对比,要想成功对比前后引用类型状态时不能直接用setState更改引用类型状态的属性
正确用法:
class Hi extends React.PureComponent{//创建纯组件
state={
obj:{
number:0
}
}
changeState=()=>{
const newObj={...this.state.obj,number:Math.floor(Math.random()*3)}//创建新的对象
this.setState(()=>{
return {obj:newObj}//直接替换引用数据类型
})
}
render(){...}
}
虚拟DOM和Diff算法
调用render(),不意味着页面全部重新渲染,只是使用Diff算法比对两次的虚拟DOM对象,选择性更新
React路由
react路由简介
React 路由(React Router)是一个用于构建单页面应用程序(Single Page Applications,SPA)的库,它允许你在 React 应用中实现页面导航和路由控制。路由是指确定在 Web 应用程序中哪个页面应该在用户导航或操作时呈现的过程。React 路由可以帮助你在不刷新整个页面的情况下更新视图,以实现更流畅的用户体验。
react路由基本使用
- BrowserRouter:BrowserRouter 使用时,通常用来包住其它需要路由的组件,所以通常会需要在你的应用的最外层用它
import React from 'react';
import ReactDOM from 'react-dom';
import App from './lxRouter';
//导入
import { BrowserRouter } from 'react-router-dom';
{/*BrowserRouter 使用时,通常用来包住其它需要路由的组件,所以通常会需要在你的应用的最外层用它*/}
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
- Link:可以使用 Link 组件来创建常规链接,类似于a标签,
//to中写链接的路径
<Link to="/about">about</Link>
- Routes:绝大多数情况下,Routes 的唯一作用是用来包住路由访问路径(Route)的
- Route:Route 组件来定义所有路由。该组件接受两个 props:
- path:页面 URL 应导航到的路径
- element:页面导航到该路由时加载的元素
- '/'是默认路径,如果想要在所有 Route 都不匹配时就渲染 404 页面,只需将 404 页面对应的 Route 的 path 设置为 *
//导入
import { Routes, Route, Link,useNavigate } from "react-router-dom"
import React from 'react';
function App() {
return (
<div className="App">
<header className="App-header">
{/*绝大多数情况下,Routes 的唯一作用是用来包住路由访问路径(Route)的*/}
<Routes>
{/*Route 用来定义一个访问路径与 React 组件之间的关系,path中便是访问路径,'/'是默认路径,'*'链接所有 Route 都不匹配时就渲染的页面*/}
<Route path="/" element={<FrontPage />}></Route>
<Route path="/home" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</header>
</div>
);
}
class FrontPage extends React.Component{
render(){
return (<div>
<Link to="/about">about</Link>
</div>)
}
}
function Home(){
return <div>
<main>
<h2>Welcome to the homepage</h2>
</main>
<Link to="/">FrontPage</Link>
<Link to="/about">about</Link>
</div>
}
function About() {
return <div>
<h2>Welcome to the about page</h2>
<nav>
<Link to="/">FrontPage</Link>
<Link to="/home">home</Link>
<Link to="/about">about</Link>
</nav>
</div>
}
export default App;
编程式导航
命令式导航方法:useNavigate Hook
function Home(){
//编程式导航1:创建一个useNavigate()
const navigate = useNavigate()
function handleClick(){
//编程式导航2:参数中传入目标url
return navigate('/about')}
return <div>
<main>
<h2>Welcome to the homepage</h2>
</main>
<button onClick={handleClick}>about</button>
<Link to="/">FrontPage</Link>
<Link to="/about">about</Link>
</div>
}
Navigate组件是一种声明式的导航方式。使用 Navigate 组件时,首先需要从 react-router-dom 导入 Navigate 组件。然后在 Navigate 组件中通过 to props 来指定要跳转的路径
//首先需要从 react-router-dom 导入 Navigate 组件
import { Routes, Route, Link,useNavigate,Navigate } from "react-router-dom"
function App() {
return (
<div className="App">
<div>App</div>
<header className="App-header">
<Routes>
<Route path="/" element={<FrontPage />}></Route>
<Route path="/home" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
{/*在 Navigate 组件中通过 to props 来指定要跳转的路径*/}
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</header>
</div>
);
}
axios
什么是Axios?
Axios是一个HTTP客户端库,它允许你向一个给定的端点(endpoint)发出请求
使用方法:
安装与引入
- 在React项目中安装axios:
npm install axios
- 然后在react文件中导入axios:
import axios from 'axios';
发送GET请求
使用axios.get方式:
//在此处填入url,这里写的是第一种传参方式
axios.get('https://www.bilibili.com/', params: {
id: 12345// 这里是url携带的参数
})
.then(function (response) {
// then里是请求成功后的操作,response是请求后返回的结果
console.log(response);
this.setState({
...
})
})
.catch(function (error) {
// catch里是请求失败后的操作,error是请求后返回的错误
console.log(error);
});
注意: response.data是服务器返回的数据对象,response.status是服务器返回的状态码
使用axios(config {…}):
axios({
method: 'get',//请求类型
url: 'https://www.bilibili.com/',
params: {
id: 12345,
}
})
.then(function (response) {
console.log(response);
}
.catch(function (error) {
console.log(error);
});
发送POST请求
使用axios.post方式:
axios.post('https://www.bilibili.com/', {
id: '123',//这些是要post的数据,就像ajax里的requestBody参数
name: 'bob'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
使用axios(config {…})方式:
// Send a POST request
axios({
method: 'post',
url: 'https://www.bilibili.com/',
data: {
id: '123',//这些是要post的数据,就像ajax里的requestBody参数
name: 'bob'
}
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
同时发送多个请求
function getUserAccount() {
return axios.get('https://www.bilibili.com/');
}
function getUserPermissions() {
return axios.get('https://www.jd.com/');
}
axios.all([getUserAccount(), getUserPermissions()])//发送两个请求
.then(axios.spread(function (acct, perms) {
// 两个请求都完成后
}));