React——基础2(笔记)

一、setState

1.1 更新数据

setState是异步更新数据

// App.js
import React from 'react'
class App extends React.Component{
    state = {
        count:1
    }
    handleClick = (e)=>{
        this.setState({
            count: this.state.count + 1
        })
        // 输出旧值
        console.log(this.state.count)
    }
    handleClick2 = (e)=>{
        this.setState({
            count: this.state.count + 1
        })
        // 输出旧值
        console.log(this.state.count)
        // 旧值+1
        this.setState({
            count: this.state.count + 1
        })
        console.log(this.state.count)
    }
    render(){
        return (
        	// 多次调用setState,只会render 1次
        	console.log("render");
            <div>
                {this.state.count}<br/>
                <button onClick={this.handleClick}>add+1</button><br/>// + 1
                <button onClick={this.handleClick2}>add+1+1</button>// 仍是 +1
            </div>
        )
    }
}

export default App

在这里插入图片描述

1.2 回调的setState

	handleClick = (e)=>{
        // 也是异步的
        this.setState((state,props)=>{
            return{
                count: state.count + 1
            }
        })
        this.setState((state,pros)=>{
            console.log("第二次调用",state.count)
            return{
                count: state.count + 1
            }
            
        })
        // 同时因为异步,该输出会先于前面
        console.log(this.state.count)
    }

在这里插入图片描述

	handleClick = (e)=>{
        
        this.setState(
            (state,props)=>{
                return{
                    count: state.count + 1
                }
            },
            ()=>{
                console.log("状态更新后的值",this.state.count)
            }
        )
        console.log("状态更新前的值",this.state.count)
    }

在这里插入图片描述

1.3 组件的更新

setState作用

  1. 修改state
  2. 更新组件(UI)

组件更新会更新自己以及自己的所有子节点。

父节点和兄弟节点不会更新。

二、JSX语法的转化过程

JSX->createElement()->React元素

JSX

const element = (
	<h1 className="greeting">
		Hello JSX!
	</h1>
)

createElement()

const element = React.createElement(
	'h1',
	{className:'greeting'},
	'Hello JSX!'
)

React元素

const element = {
	type:'h1',
	props:{
		className: 'greeting'
		children: 'Hello JSX!'
	}
}

三、组件性能的优化

3.1 减轻state

state:只存储与组件渲染相关的数据
不做渲染的数据不要放在state中,直接放在this之中即可

3.2 避免不必要的重新渲染

组件的更新策略,会导致不必要的渲染也会发生(子组件没有变化)

使用 shouldComponentUpdate(nextProps,nextState)

  • 返回true就会渲染
  • 返回false就不会渲染
// App.js
import React from 'react'
class App extends React.Component{
    state = {
        count:1
    }
    handleClick = (e)=>{
        
        this.setState((state,props)=>{
                return {
                    count: state.count + 1
                }
            },
            ()=>{
                console.log(this.state.count)
            }
        )
    }
    /**
     * @param nextProps 最新的props
     * @param nextState 最新的状态
     */
    shouldComponentUpdate(nextProps,nextState){
        console.log(nextProps)
        console.log(nextState)
        return false
    }
    render(){
        return (
            <div>
                {this.state.count}<br/>
                <button onClick={this.handleClick}>add+1</button>
            </div>
        )
    }
}

export default App

随机数示例略。

对于可能有多个值会变化的情况,我们可以将它们分到子组件去,然后通过子组件去检测是否需要更新。同时这样更新的范围也会小很多。

3.3. 纯组件

React.PureComponent与React.Component功能相似

PureComponent自动实现了shouldComponentUpdate,不需要手动比较

内部通过分别比较前后两侧props和state的值,来决是否重新渲染组件

示例略。

shallow compare 浅对比

(可以通过:浅拷贝和深拷贝来理解)

官方文档-性能优化

官方文档说明

大部分情况下,你可以使用 React.PureComponent 来代替手写 shouldComponentUpdate。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。当数据结构很复杂时,情况会变得麻烦。
例如,你想要一个 ListOfWords 组件来渲染一组用逗号分开的单词。它有一个叫做 WordAdder 的父组件,该组件允许你点击一个按钮来添加一个单词到列表中。以下代码并不正确:

class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
}

class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // 这部分代码很糟,而且还有 bug
    const words = this.state.words;
    // 不会生成新的数组words
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

问题在于 PureComponent 仅仅会对新老 this.props.words 的值进行简单的对比。由于代码中 WordAdder 的 handleClick 方法改变了同一个 words 数组,使得新老 this.props.words 比较的其实还是同一个数组。即便实际上数组中的单词已经变了,但是比较结果是相同的。可以看到,即便多了新的单词需要被渲染, ListOfWords 却并没有被更新。

可以用不可变数据解决。(也就是避免产生对象、引用)可以参考官方文档的不可变数据部分。

用如下方式获取一个新的数组words

handleClick() {
  this.setState(state => ({
    words: [...state.words, 'marklar'],
  }));
};

handleClick() {
  this.setState(state => ({
    words: state.words.concat(['marklar'])
  }));
}

用如下方式获取一个新的对象colormap

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

如下方法不会生成新的对象colormap

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

3.4 虚拟DOM和Diff算法

React更新视图的思想是:只要state变化就重新渲染和视图

DOM元素需要更新时,也是部分更新。

React使用虚拟DOM配合Diff算法做到的。

虚拟DOM本质上是一个JS对象,用来描述你希望在屏幕上看到的(UI)内容。也即是我们前面提到的React元素。

  1. 初次渲染时,React会根据初始state(Model)创建一个虚拟DOM对象(树)
  2. 根据虚拟DOM生成生成真正的DOM,渲染到页面中
  3. 当数据变化后,重新根据新的数据,创建新的虚拟DOM对象(树)
  4. 与上一次得到的虚拟DOM对象,使用DIFF算法对比,得到需要更新的内容
  5. 最终,React只将变化的内容更新大DOM中,重新渲染到页面中

render方法调用,并不意味者浏览器重新渲染,仅仅说明进行DIFF

四、路由

4.1 路由介绍

现代前端应用大多数都是SPA(单页面应用程序),也就是只有一个HTML页面程序。

为了有效的使用单个页面来管理原来多个页面功能,前端路由应运而生。

  • 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
  • 前端路由是一套映射规则,在React中,是URL路径与组件对应关系
  • 使用React路由简单来说,就是配置路径和组件(配对)

4.2 路由使用步骤

注意,视频中使用的是V5版本,笔者这里使用的V6,因此将不会完全按照视频中的来做。

npm install react-router-dom

可参考文档-第三方库-路由

也可以使用视频中的版本

npm install react-router-dom@5.2.0

导入路由的三个核心组件: Router/Route/Link 以及V6的Routes

import React from 'react';
import ReactDOM from 'react-dom/client';

import {BrowserRouter as Router,Route,Routes,Link} from 'react-router-dom'

const First = ()=>(
    <p>页面一内容</p>
)

const App = () => {
    return(
    <Router>
        <div className="App" >
            <h1>React路由基础</h1>
            {/* 指定入口,导航 */}
            <Link to="/first">页面一</Link>
            <Routes>
                {/* 指定路由出口,内容 */}
                <Route path='/first' element={<First />}/>
            </Routes>
        </div>
    </Router>
    )
}
const reactDom = ReactDOM.createRoot(document.getElementById('root'))

reactDom.render(<App/>)

在这里插入图片描述

4.3 常用组件

  • Router组件:包裹整个应用,一个React应用只需要使用一次

常用的Router:HashRouter和BrowserRouter

HashRouter使用URL的哈希值实现

在这里插入图片描述

  • Link组件:指定导航链接(a标签);to属性:浏览器地址栏中的pathname ,一个导航,与Rote中的Path一致。他是一种特别的Link,主要是Link每次会重新渲染,而它不会。
  • Routes组件:形成路由渲染分支,渲染其下路由。(V5,Route自己负责自己)
  • Route组件:指定路由展示组件相关信息。同时,Route在哪,内容就渲染在哪。

老版本:Router-Switch-Route
新版本:Router-Routes-Route

	<Router>
        <div className="App" >
            <Routes>
                {/* 指定路由出口,内容 */}
                <Route path='/first' element={<First />}/>
            </Routes>
            <h1>React路由基础</h1>
            {/* 指定入口,导航 */}
            <Link to="/first">页面一</Link>
           
        </div>
    </Router>

在这里插入图片描述

4.4 编程式导航

import React from 'react';
import ReactDOM from 'react-dom/client';

import {BrowserRouter as Router,Route,Routes,Link} from 'react-router-dom'
import Home from './component/Home';
import Login from './component/Login';


const App = () => {
    return(
    <Router>
        <div className="App" >
            <h1>React路由基础</h1>
            {/* 指定入口,导航 */}
            <Link to="/login">登录页面</Link>
            <Routes>
                {/* 指定路由出口,内容 */}
                <Route path='/login' element={<Login />}/>
                <Route path='/home' element={<Home />}/>
            </Routes>
        </div>
    </Router>
    )
}

const reactDom = ReactDOM.createRoot(document.getElementById('root'))

reactDom.render(<App/>)

import React from 'react'

class Home extends React.Component{
    render(){
        return (<div>后台页面</div>)
    }
}

export default Home

注意,新版本中,没有了history,因此navigate作为代替,但是它是一种hook,react中类组件不能用hook(因为hook目的就是为了代替类)。因此我们只能用函数的方式完成。

同时,其中填入负数,也就是回退多少页。它有第二个参数,用于传参。

import React from 'react'
import { useNavigate } from 'react-router-dom' 

function Login(){
    const navigate =  useNavigate();
    const handleLogin = () =>{
        //...
        // 编程式导航跳转
        navigate('/home')
    }        

    return(
        <div>
            <div>登录页面:</div>
            <button onClick={()=>{handleLogin()}}>登录</button>
        </div>
    )          
            
}

export default Login

在这里插入图片描述
在这里插入图片描述

4.5 默认路由

进入页面时匹配的路由
就是 path:/
此处login就是默认路由

			<Routes>
                {/* 指定路由出口,内容 */}
                <Route path='/' element={<Login />}/>
                <Route path='/home' element={<Home />}/>
            </Routes>

4.6 模糊匹配模式

如下(笔者这里没有遇到该问题,可能是脚手架或者版本已经处理了该问题。

一般情况下,可以加一个exact <Route exact path=‘/detail’…>进行精确匹配。

path(Routes)to(link)
/会匹配所有的路由
/first会匹配 /first 、/first/a
			<Routes>
                {/* 指定路由出口,内容 */}
                <Route path='/' element={<Home />}/>
                <Route path='/login' element={<Login />}/>
            </Routes>

参考

[1]黑马程序员前端React视频教程,react零基础入门原理详解到好客租房项目实战
[2]官方文档
[3]第三方库-路由

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值