蓝旭自学自讲,React进阶

组件的生命周期(类组件才有)

概念:

  • 生命周期:组件从被创建到挂载到页面中运行,再到组件不用时被卸载的过程。
  • 钩子函数:生命周期各个阶段伴随的被调用的方法。提供了某个阶段的重要时机。

请添加图片描述

创建时(挂载阶段):

  • 时机:页面刚加载完时(组件创建时)
  • 钩子函数执行顺序: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) {
    // 两个请求都完成后
  }));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值