React
一、react的简介
1.什么是react
react是一个用于构建用户界面的js库,react起源于facebook的内部项目,目前已经是市面上最流行的前端开发框架之一。
2.react的特点
react具有三个特点:组件化、声明式UI、学习一次,随处使用
①组件化
组件是react中最重要的部分,组合与复用多个组件来实现页面的功能
②声明式UI
只需描述页面的构造,就可以生成什么样的页面,就像我们写的html
③学习一次,随处使用
使用react可以开发web端,移动端,甚至是虚拟现实
二、react的基本使用
1.创建基本结构
// 导入创建标签的react
import React from 'react'
// 导入挂载标签的react-dom
import ReactDOM from 'react-dom'
// 创建标签:React.createElement( '标签名' , {标签身上的属性(没有可以是空对象或null)} , '标签包含的内容' )
const h1 = React.createElement('h1', null, 'v-v')
const a = React.createElement('a', { href: 'https://www.baidu.com' }, '去往百度')
// 挂载标签到root节点上(public/index.html),ReactDOM.render(接收所创建标签元素的变量(注意是变量),获取root节点(固定写法))
ReactDOM.render(h1, document.querySelector('#root'))
ReactDOM.render(a, document.querySelector('#root')) // 会覆盖前一个
2.创建嵌套结构
import React from 'react'
import ReactDOM from 'react-dom'
// 注意写类名的规范写法是classname,而不是class
ReactDOM.render(React.createElement('ul',{className:'box'},React.createElement('li',null,'香蕉'),React.createElement('li',null,'苹果'),React.createElement('li',null,'橘子')),document.querySelector('#root'))
三、jsx语法糖
jsx其实就是React.createElement的与语法糖,语法糖的意思就是jsx的本质和React.createElement是一样的,只是一种便捷的写法,编译器会帮我们做转换。语法糖可以增加程序的可读性,更方便程序员的使用
1.jsx基础写法
import React from "react";
import ReactDOM from "react-dom";
// 这就是jsx的写法,本质其实还是React.createElement,你将这个变量再用React.createElement创建打印出来还是一样的
const ulDOM = (
{/* 注意,jsx语法和vue一样都必须要有个根标签 */}
{/* 因为class是js里的关键字,为了避免冲突,给元素设置类名都用classname。for也替换成htmlfor */}
<ul className="list">
<li>七海</li>
<li>阿梓</li>
<li>小可</li>
</ul>
);
ReactDOM.render(ulDOM, document.querySelector("#root"));
jsx可以帮我们减少代码操作,我们只需要直接写标签然后赋值即可
2.jsx插值表达式-基础数据类型
import React from "react";
import ReactDOM from "react-dom";
const msg = '114514'
// 在jsx里,插值表达式与vue不同,是一个{}
// 并且null,undefined,Boolean放在插值表达式里不显示(内容为空)
const h1 = <>
{/* 可以像vue一样在标签内容里显示变量 */}
<h1>{msg}</h1>
{/* 以下的标签内容都不会显示 **/}
<h1>{null}</h1>
<h1>{undefined}</h1>
<h1>{true}</h1>
<h1>{false}</h1>
</>
ReactDOM.render(h1, document.querySelector('#root'))
3.jsx插值表达式-引用数据类型
import React from "react";
import ReactDOM from "react-dom";
const arr = [1, 2, 3]
const arr1 = [<div>1</div>, <div>2</div>, <div>3</div>]
const obj = {
name: 'zs',
age: 18
}
const fn = () => 'abc'
// 插值表达式里放数组元素的话,react会把数组的每个元素当成单独的节点去渲染
const dom = <>
<h1>{arr}</h1>
<h1>{arr1}</h1>
{/*react不支持插值表达式里放对象,放了就会报错。放在插值表达式里的只能是对象的属性(对象的属性也不能是对象)*/}
<h1>{obj.name}</h1>
{/* react的插值表达式不能直接放函数本身(因为函数也是对象),只能放函数的调用(函数的返回值不能是对象,是对象也会报错)*/}
<h1>{fn()}</h1>
</>
ReactDOM.render(dom, document.querySelector('#root'))
4.jsx插值表达式-三元/逻辑运算符
import React from "react";
import ReactDOM from "react-dom";
const msg = 1145
const html = <h2>{msg}</h2>
// react的插值表达式也可以放三元和逻辑运算符
// 因为ifelse写法和三元运算符写法一致,故在此不表
const dom = <>
<div>{msg === 1145 ? html : 'inm'}</div>
<div>{msg > 18 && 18}</div>
</>
ReactDOM.render(dom, document.querySelector('#root'))
5.jsx实现标签的隐藏显示
在vue2时,我们动态实现标签的隐藏和显示是通过v-if和v-show来实现。在react中,则是通过之前所讲到的null,undefined,Boolean值在标签上不显示的效果来实现的
import React from "react";
import ReactDOM from "react-dom";
const show = false
const vif = (show) => {
return show ? <h1>显示了</h1> : null
}
// react控制显示隐藏原理是删除dom节点(删除的是用于显示的标签而不是包着的标签),不是控制样式
const dom = <>
<div>{vif(show)}</div>
<h1>{show && '显示'}</h1>
</>
ReactDOM.render(dom, document.querySelector('#root'))
四、react设置样式
1.设置行内样式
import React from "react";
import ReactDOM from "react-dom";
// 内部样式
// 注意写法是style={{css样式:值, ...}}
const dom = <ul style={{ backgroundColor: 'red', fontSize: 40 }}>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
ReactDOM.render(dom, document.querySelector('#root'))
这种方式平时很少使用
2.设置外联样式
import React from "react";
import ReactDOM from "react-dom";
// 外部引入样式
import './base.css'
const dom = <ul>
{/* 写入类名 */}
<li className="list">1</li>
<li>2</li>
<li>3</li>
</ul>
ReactDOM.render(dom, document.querySelector('#root'))
五、函数式组件
import React from 'react'
import ReactDOM from 'react-dom'
// 函数式组件
// 1.函数组件名首字母必须大写,声明函数后作为标签使用
// 2.如果组件不想返回内容,那么就返回null,不能返回undefined
const Hello = () => <h1>react</h1> // 返回的是jsx代码
const dom = <Hello></Hello>
ReactDOM.render(dom, document.querySelector('#root'))
tips:函数组件的本质其实就是一个js函数。和之前的函数返回值作为插值表达式不同,函数组件是作为标签去使用,所以在声明时首字母必须大写,否则react就不认识你这个标签
六、class组件
1.class构造函数
class组件其实就是使用class构造函数去继承React.Component对象的属性和方法
class创建构造函数笔者在之前的笔记中有写过,在此不过多表述
2.class组件
import React from 'react'
import ReactDOM from 'react-dom'
// class组件和函数组件的使用大致相同
// class组件创建固定写法 class 组件名 extends React.Component {} ,表示继承React.Component这个构造函数的属性和方法
class MyDom extends React.Component {
// class组件内必须要有render函数,render函数在class组件创建时自动执行,返回值规则和函数式组件相同
render() {
return <h1>这是render创建的组件</h1>
}
}
const dom = <div><MyDom></MyDom></div>
ReactDOM.render(dom, document.querySelector('#root'))
另一种写法(解构)
// 将Component这个对象解构出来
import {Component} from 'react'
import ReactDOM from 'react-dom'
// 这里就不用写extends React.Component
class MyDom extends Component {
render() {
return <h1>这是render创建的组件</h1>
}
}
const dom = <div><MyDom></MyDom></div>
ReactDOM.render(dom, document.querySelector('#root'))
3.class组件的状态
在react前期,只有类组件能声明状态,但是函数式组件无法声明状态。所以类组件被称为容器组件,而函数组件被称为展示组件
react的状态其实就是vue的data,用于存放数据
import React, { Component } from 'react'
export default class App extends Component {
state = {
msg: 'hello react'
}
render() {
return (
<div>{this.state.msg}</div>
)
}
}
七、react事件
1.事件绑定
import React, { Component } from 'react'
export default class App extends Component {
state = {
msg: 'hello react'
}
render() {
return (
<>
<button onClick={() => console.log(123)}>简写写法</button>
// 注意这里是放入函数本身,不用加()
<button onClick={this.clickFn}>多行逻辑写法</button>
</>
)
}
clickFn() {
console.log(456);
}
}
2.阻止默认事件
import React, { Component } from 'react'
export default class App extends Component {
state = {
msg: 'hello react'
}
render() {
return (
<>
<a href='http://www.baidu.com' onClick={(e) => e.preventDefault()}>阻止A标签跳转-简写写法</a><br />
<a href='http://www.baidu.com' onClick={this.clickFn}>阻止A标签跳转-多行逻辑写法</a>
</>
)
}
clickFn(e) {
e.preventDefault()
}
}
3.事件传参
import React, { Component } from 'react'
export default class App extends Component {
state = {
msg: 'hello react'
}
render() {
return (
<>
{/* 利用函数里调用另一个函数的方法进行传参(因为在react中不能像vue一样直接在onClick里进行传参,react的{}里不能直接放函数的调用) */}
<button onClick={(e) => this.clickFn(this.state.msg, e)}>传参</button>
</>
)
}
clickFn(msg, e) {
console.log(e);
console.log(msg);
}
}
4.this指向问题
React自带结构体中,源码内部处理了this, this指向组件实例对象,React非自带结构体中,this指向undefined
import React, { Component } from 'react'
console.log('最外层', this); // undefined
export default class App extends Component {
state = {
msg: 'hello react'
}
render() {
console.log('render里', this);
return (
<>
{/* 利用函数里调用另一个函数的方法进行传参(因为在react中不能像vue一样直接在onClick里进行传参,react的{}里不能直接放函数的调用)*/}
// 这里的this指向app实例
<button onClick={(e) => console.log('绑定事件里', this)}>绑定事件的this</button><br />
<button onClick={this.clickFn}>自定义事件的this</button>
</>
)
}
clickFn() {
console.log('自定义事件里', this); // 指向undefined
}
}
改变this指向方法
import React, { Component } from 'react'
export default class App extends Component {
state = {
msg: 'hello react'
}
render() {
return (
<>
{/* 调用时处在render环境里,所以指向的是实例对象 */}
<button onClick={(e) => this.clickFn()}>解决this指向问题-在绑定事件里使用箭头函数</button><br />
<button onClick={this.arrowClickFn}>自定义事件的this</button>
</>
)
}
clickFn() {
console.log('this', this);
}
// 将调用的函数变成箭头函数写法(调用时处在App构造函数里,指向app实例对象)
arrowClickFn = () => {
console.log(this);
}
}
八、react数据不可变
react官网一直强调state里的数据不可变,那为什么state的数据要用不可变值呢?
1.什么是不可变值
不可变数据其实就是const声明的常量,不可以再次改变
const a = 1
a = a + 1 // 这样会报错,因为a不可变
2.为什么React里的setState一定要用不可变值
为了性能的优化,react在更新过程中,有 shouldComponentUpdate 这个生命周期函数,
它具有拦截更新渲染的作用,如果shouldComponentUpdate返回值是true,则去更新渲染,反之则不会更新
当我们使用会改变原数据的方法(例如数组的push,splice等方法,++等重新对数据进行赋值)或者赋值时,有可能会出现父组件的数据修改了,但是子组件没有更新(当子组件使用shouldComponentUpdate比较新旧props时),因为它们的引用地址没有发生变化
// 例1,数组的push方法
this.state.list.push('d') // 此时已经改变了原来的数组,相当于已经进行了一次更新
this.setState({
list: this.state.list
}) // 改变原数组后又进行setState,虽然没报错,但是已经错了
// 例2,++等重新赋值
this.state.num++
this.setState({
num: this.state.num,
})
// 这个也是修改原数据后又进行了一次setState
但为什么我们使用错误的写法视图也能更新呢?因为 shouldComponentUpdate 默认返回true
正确的写法
const newList = this.state.list
newList.push('d')
const newNum = num + 1
this.setState({
list:newList,
num:newNum
})
九、受控组件
受控组件就是表单元素的值收到state的控制,非受控组件就是表单元素的值不受state控制,由dom本身管理。
受控组件其实就是vue中的v-model绑定的表单元素
import React, { Component } from 'react'
export default class App extends Component {
state = {
name: '卦者灵风',
isSingle: false
}
render() {
const { name, isSingle } = this.state
return (
<div>
姓名:<input type="text" value={name} onChange={(e) => this.changeFn(e, 'name')} />
<br />
是否单身:<input type="checkbox" checked={isSingle} onChange={(e) => this.changeFn(e, 'isSingle')} />
</div>
)
}
changeFn({ target: { checked, value } }, type) {
this.setState({ [type]: type === 'isSingle' ? checked : value })
}
}
react实现v-model的原理需要两步,第一步是绑定value值为state数组。第二步是监听表单事件(react的表单改变统一使用onChange事件),当表单的value发生改变,使用setState去更新state的值
十、ref获取元素
1.ref获取标签元素
import React, { Component } from 'react'
import Footer from '../src/components/Footer'
// 使用ref需要三步
export default class App extends Component {
// 1.创建ref对象
Ref = React.createRef()
render() {
return (
// 2.绑定ref属性
<>
<div onClick={this.clickFn} ref={this.Ref}>ref获取dom元素</div>
</>
)
}
clickFn = () => {
// 3.使用ref
console.log(this.Ref);
}
}
2.ref获取组件
import React, { Component } from 'react'
import Footer from './components/Footer'
// 使用ref需要三步
export default class App extends Component {
// 1.创建ref对象
Ref = React.createRef()
render() {
return (
// 2.绑定ref属性
<>
{/* <div onClick={this.clickFn} ref={this.Ref}>ref获取dom元素</div> */}
<Footer ref={this.Ref}></Footer>
<button onClick={this.clickFn}>获取组件</button>
</>
)
}
clickFn = () => {
// 3.使用ref
console.log(this.Ref);
}
}
tips:注意,ref获取不了函数组件
十一、组件通讯
1.父传子
import React, { Component } from 'react'
export default class App extends Component {
state = {
name: 'zs',
count: 100
}
render() {
return (
<div>
{/* 父组件传参直接通过设置标签属性方式去传递 */}
<MyFn name={this.state.name} count={this.state.count}></MyFn>
<MyClass name={this.state.name} count={this.state.count}></MyClass>
</div>
)
}
}
// 函数式组件通过形参props接收父组件传过来的参数
const MyFn = ({ name, count }) => {
console.log(name, count);
return <h1>这是函数组件</h1>
}
class MyClass extends Component {
render() {
// class组件通过this.props来接受父组件传过来的参数
const { name, count } = this.props
return <h1>这是class组件 - {name} - {count}</h1>
}
}
tips:props可以传任意类型的数据
import React, { Component } from 'react'
export default class App extends Component {
state = {
name: 'zs',
count: 100
}
render() {
return (
<div>
<MyClass jsx={<h1 style={{ backgroundColor: 'red' }}>这是jsx片段</h1>} fn={() => console.log('你妈妈死掉了')} obj={{ ip: '1!5!', loc: '哥们在这给你说唱' }} arr={[1, 2, 3]}></MyClass>
</div>
)
}
}
class MyClass extends Component {
render() {
// class组件通过this.props来接受父组件传过来的参数
const { jsx, fn, obj, arr } = this.props
return <>
{/* 父组件传对象 */}
<h1>{obj.ip} - {obj.loc}</h1>
{/* 父组件传jsx */}
{jsx}
{/* 父组件传数组 */}
<div>{arr}</div>
{/* 父组件传函数 */}
<button onClick={fn}>点我触发父组件传过来的函数</button>
</>
}
}
注意:props的值是只读的,形成单向数据流(也就是说你只能在父组件里修改父组件的值)。在vue中可以修改对象里的属性跳开这个限制,但是在react中你无法穿越这层壁垒(^_^)
2.子传父
import React, { Component } from 'react'
export default class Parent extends Component {
state = {
money: 1000,
};
// 父亲每次赚1000元
handleMakeMoney = () => {
this.setState({
money: this.state.money + 1000,
});
};
// 子传父函数
handleCostMoney = (number) => this.setState({ money: this.state.money - number })
render() {
const { money } = this.state
return (
<div>
<h1>{money}</h1>
<button onClick={this.handleMakeMoney}>爸爸开始赚钱了</button>
{/* 将函数通过props传给子组件 */}
<Child handleCostMoney={this.handleCostMoney} money={money / 2} ></Child>
</div>
);
}
}
class Child extends Component {
render() {
const { money, handleCostMoney } = this.props
return (
<div>
<h1>爸爸给我钱了:{money} </h1>
{/* 通过接收父组件传过来的函数,让这个函数接收参数,父组件的函数去接收这个参数,通过setState去修改值 */}
<button onClick={() => handleCostMoney(50000)}>花钱如流水</button>
</div>
);
}
}
3.跨组件通讯
/*
目标: 使用Context来跨组价通信,让App组件-直接传数据给SonSon组件
*/
import React, { Component } from 'react'
// 跨组件通讯第一步,先创建一个context对象,将里面的Provider和Consumerjsx解构出来
const { Provider, Consumer } = React.createContext()
export default class App extends Component {
state = {
name: 'father'
}
render() {
return (
// 传值的组件用Provider标签包起来,使用value属性去传值
<Provider value={this.state.name}>
<div>
<h1>父组件</h1>
<Son></Son>
</div>
</Provider>
);
}
}
class Son extends Component {
render() {
return (
<div>
<h2> 儿子</h2>
<SonSon></SonSon>
</div>
);
}
}
class SonSon extends Component {
render() {
return (
<div>
<h2> 孙子:爷组件传过来的值-
{/* 接收值的组件通过Consumer标签里插值返回一个回调函数去接收值 */}
<Consumer>
{(data) => data}
</Consumer>
</h2>
</div>
);
}
}
4.props的children属性
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<>
{/* 在子组件里标签里包着的内容会被当作props的children属性去传给子组件 */}
{/* children可以穿任意类型的参数 */}
<Child>
{/* <h1>这是jsx代码片段</h1> */}
{() => <div>这是函数返回的jsx</div>}
</Child>
</>
)
}
}
const Child = ({ children }) => {
return <>
<h1>我是子组件</h1>
{/* children传jsx */}
{/* {children} */}
{/* <button onClick={children}>children传函数</button> */}
<h1>{children()}</h1>
</>
}
tips:通过children属性,可以去模仿vue里的插槽
例如vue的作用域插槽
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<>
{/* 模仿作用域插槽,其实就是子传父,父通过子的传值去选择性的渲染子的结构 */}
<ScopeSlot>{(list) => <ul>{list.map((item, index) => <li key={index}>{item}</li>)}</ul>}</ScopeSlot>
</>
)
}
}
class ScopeSlot extends Component {
state = {
list: ['正宗', '大', '菲柱']
}
render() {
const { children } = this.props
return <div>
{children(this.state.list)}
</div>
}
}
5.类型校验
当我们传值时,有可能因为值类型传递错误而引发bug。那么当react之前没有使用ts时,是怎么解决的呢?
import React, { Component } from 'react'
// 导入类型校验的包
import PropTypes from 'prop-types'
export default class App extends Component {
render() {
return (
<TypeCheck name="zs"></TypeCheck>
)
}
}
class TypeCheck extends Component {
render() {
const { name = 'ls' } = this.props
return <div>类型校验 - {name}</div>
}
}
// 语法:校验的组件名.propTypes = {要校验的属性:PropTypes.给定的类型}
TypeCheck.propTypes = {
name: PropTypes.number
}
十二、生命周期
import React, { Component } from 'react'
export default class App extends Component {
// 对标vue中的created,但是不在这里发请求,老东西已经被淘汰了,已经不常用了(只触发一次)
constructor() {
console.log('页面创建');
super()
}
state = {
name: 'zs'
}
// 负责页面渲染的钩子函数,当页面更新时就会触发(可触发多次)
render() {
console.log('页面渲染');
return <div>{this.state.name}</div>
}
// 页面挂载完成后触发的钩子函数,与vue不同的是,请求在这里发送(只触发一次)
componentDidMount() {
console.log('页面挂载完成');
}
// 页面更新完成后(props和state的值改变都会触发)触发的钩子函数,一般用于dom操作,获取更新后的值(比如本地存储监听值变化然后存起来)
// 注意,不能在这里进行setState的值更新操作,否则会死循环(render里也不行,render也是拿到更新后的值)
componentDidUpdate() {
console.log('页面更新完成');
}
// 组件卸载阶段出发的钩子函数,对标vue的beforeDestory,主要用于解绑事件和清除定时器等
componentWillUnmount() {
}
}
// 触发顺序
// constructor(只触发一次) => render(触发多次) => componentDidMount(只触发一次) => componentDidUpdate(触发多次) => componentWillUnmount(只触发一次)
// 更新时
// render => componentDidUpdate
十三、react的setState和更新机制
1.setState的多种写法
第一种(常用,合并之前setState,render只执行一次)
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0,
name: 'zs'
}
render() {
const { num, name } = this.state
return (
<>
<h1>{num}</h1>
<h2>{name}</h2>
<button onClick={this.addNum}>+1</button>
</>
)
}
addNum = () => {
this.setState({ num: this.state.num + 1 })
this.setState({ num: this.state.num + 2 })
this.setState({ num: this.state.num + 3, name: 'ls' })
this.setState({ num: this.state.num + 4 }) // 最后更新为4
// setState更新数值是异步的,并且会合并之前所有的setState,相同属性的操作会被覆盖(为了减少操作dom次数,减少重排)
console.log(this.state.num); // 0
}
}
这个的num结果为一次+4
第二种(常用,合并之前setState,render只执行一次)
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0,
}
render() {
const { num } = this.state
return (
<>
<h1>{num}</h1>
<button onClick={this.addNum}>+1</button>
</>
)
}
addNum = () => {
this.setState((preState) => {
return {
num: preState.num + 1
}
})
// 这里的preState是上个的返回值:0 + 1 = 1
this.setState((preState) => {
return {
num: preState.num + 3
}
})
// 这里的preState是上个的返回值:1 + 3 = 4
this.setState((preState) => {
return {
num: preState.num + 4
}
})
// 结果是 4 + 4 = 8
// 以上代码相同操作不会覆盖,会把上一个执行回来的结果当成下一个的preState(render只会渲染一次)
console.log(this.state.num); // 结果为0,因为setState是异步的
}
}
这个的num结果为一次+8
第三种(不推荐,会触发三次render)
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0,
name: 'zs'
}
render() {
const { num, name } = this.state
return (
<>
<h1>{num}</h1>
<h2>{name}</h2>
<button onClick={this.addNum}>+1</button>
</>
)
}
addNum = () => {
// 这种写法最后的结果和上面第二种的执行的结果一样,但是render会渲染三次,浪费性能,不推荐
this.setState({
num: this.state.num + 1
}, () => {
this.setState({
num: this.state.num + 3
}, () => {
this.setState({
num: this.state.num + 4
})
})
})
console.log(this.state.num); // 0
}
}
这个的num结果为一次+8
2.setState的更新机制
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0
}
render() {
// 父组件更新,里面的子组件全部更新(不管子组件内有没有使用到父组件的值)
console.log('App更新了');
return (
<>
<div onClick={this.addFn}>App</div>
<Child1></Child1>
<Child2></Child2>
</>
)
}
addFn = () => this.setState({ num: this.state.num + 1 })
}
class Child1 extends Component {
render() {
console.log('Child1更新了');
return (
<div>child1</div>
)
}
}
class Child2 extends Component {
state = {
num: 0
}
render() {
// 兄弟组件之间,自己更新不影响旁边的兄弟
console.log('Child2更新了');
return (
<>
<div onClick={this.addFn}>Child2
<Son></Son>
</div>
</>
)
}
addFn = () => this.setState({ num: this.state.num + 1 })
}
class Son extends Component {
render() {
console.log('Son更新了');
return (
<div>Son</div>
)
}
}
总结:父组件更新子组件无条件更新,不管父组件有没有传值。兄弟组件之间,自己更新不会影响到兄弟
3.优化react的更新
①使用shouldComponentUpdate阻止无用更新
import React, { Component } from 'react'
export default class App extends Component {
state = {
num: 0,
str: 'abc'
}
render() {
const { num, str } = this.state
return (
<>
<div>{num}</div>
<div>{str}</div>
<button onClick={this.addNumFn}>更新num</button>
<button onClick={this.addStrFn}>更新str</button>
<Child1 num={num}></Child1>
</>
)
}
addNumFn = () => this.setState({ num: this.state.num + 1 })
addStrFn = () => this.setState({ str: this.state.str + 'd' })
}
class Child1 extends Component {
// 使用shouldComponentUpdate可以判断组件是否更新,函数形参接收最新的props的值
shouldComponentUpdate(newProps) {
// 当新旧值不一样就更新
if (newProps.num !== this.props.num) return true
// 一样就不更新
return false
}
render() {
console.log('Child1更新了');
return (
<div>child1</div>
)
}
}
②使用PureComponent(底层也是使用shouldComponentUpdate判断)
import React, { Component, PureComponent } from 'react'
export default class App extends Component {
state = {
num: 0,
str: 'abc'
}
render() {
const { num, str } = this.state
return (
<>
<div>{num}</div>
<div>{str}</div>
<button onClick={this.addNumFn}>更新num</button>
<button onClick={this.addStrFn}>更新str</button>
<Child1 num={num}></Child1>
</>
)
}
addNumFn = () => this.setState({ num: this.state.num + 1 })
addStrFn = () => this.setState({ str: this.state.str + 'd' })
}
class Child1 extends PureComponent {
render() {
console.log('Child1更新了');
return (
<div>child1</div>
)
}
}
tips:shouldComponentUpdate是浅比较,什么是浅比较呢?在讨论这个问题之前,笔者先举两个例子
①这个式子结果是什么
console.log({} === {})
答案是false
②这个式子结果是什么
console.log([] === [])
答案也是false
这是为什么呢?因为无论是[]还是{}都是通过new Array和new Object创建出来的,他们创建时地址不同,所以进行比较时返回结果为false
那么这两个例子和浅比较有什么关系呢?答:浅比较不会比较地址的变化
所以当你使用push进行数组的更新时,并没有修改地址。所以在进行更新判断时新值和旧值相等,视图就不会进行更新。这就是笔者上文提到的,为什么react中的state要使用不可变数据。并且推荐新值覆盖旧值。
十四、路由
1.使用原生js模仿路由
import React from 'react'
export default class App extends React.Component {
state = {
currentHash: '/home'
}
componentDidMount() {
// 因为一开始挂载时没有haschange事件,先改变state里的值更新成当前页面
this.setState({ currentHash: window.location.hash.slice(1) })
// 监听页面的haschange事件,每次改变都将最新值设置给currentHash
window.addEventListener('hashchange', () => {
this.setState({ currentHash: window.location.hash.slice(1) })
})
}
render() {
const { currentHash } = this.state
return (
<div>
<h1>app组件</h1>
<ul>
<li>
<a href="#/home">首页</a>
</li>
<li>
<a href="#/my">我的音乐</a>
</li>
<li>
<a href="#/friend">我的朋友</a>
</li>
</ul>
{currentHash === '/home' && <Home></Home>}
{currentHash === '/my' && <MyMusic></MyMusic>}
{currentHash === '/friend' && <Friend></Friend>}
</div>
)
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
2.路由的配置
路由配置分为四步
import React from 'react'
// 第一步,下包导入:npm i react-router-dom@5.3进行一个包的下,然后导入三个组件
// BrowserRouter表示不带#的历史模式,HashRouter表示带#的hash模式
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
export default class App extends React.Component {
render() {
return (
// 第二步,用HashRouter将app中的所有内容包起来(HashRouter标签全局只能出现一次)
<Router>
{/* 第四步,配置link跳转(对标vue的router-link) */}
<Link to='/home'>去到首页</Link>
<br />
<Link to='/my'>去到我的页面</Link>
<br />
<Link to='/friend'>去到朋友页面</Link>
{/* 第三步,使用Route配置路由规则和挂载点(放在哪个标签里就表示挂载在哪个标签里) */}
<div className='router'>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
</div>
</Router>
)
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐组件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
3.NavLink组件的使用
import React from 'react'
import { BrowserRouter as Router, Route, Link, NavLink } from 'react-router-dom'
import './app.css'
export default class App extends React.Component {
render() {
return (
<Router>
{/* 使用NavLink标签可以实现点击高亮效果,使用activeClassName可以自定义高亮类名名字 */}
<NavLink to='/home' activeClassName='highLight'>去到首页</NavLink>
<br />
{/* 使用exact可以实现精准匹配(当url === to的路径,才会触发高亮),默认为模糊匹配(url包含to,而不是to包含url) */}
<NavLink to='/my' exact activeClassName='highLight'>去到我的页面</NavLink>
<br />
<Link to='/friend'>去到朋友页面</Link>
<div className='router'>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
</div>
</Router>
)
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐组件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
4.Route路由组件的使用
import React from 'react'
import { BrowserRouter as Router, Route, Link, NavLink } from 'react-router-dom'
import './app.css'
export default class App extends React.Component {
render() {
return (
<Router>
<NavLink to='/home'>去到首页</NavLink>
<br />
<NavLink to='/my' >去到我的页面</NavLink>
<br />
<NavLink to='/friend' >去到朋友页面</NavLink>
<div className='router'>
{/* 1.Route也存在exact属性,规则与NavLink组件相同 */}
<Route path="/home" exact component={Home}></Route>
{/* 2.当path为'/'(因为任意页面模糊搜索都有/),或者是不写path时,表示匹配所有页面 */}
{/* 2.1 path为'/'通常和exact一起使用 */}
<Route path="/" component={Home}></Route>
<Route component={Home}></Route>
{/* 3.当有多个页面path相同时,不会像vue一样只匹配第一个,而是全部匹配出来,显示多个组件 */}
<Route path="/my" component={MyMusic}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
</div>
</Router>
)
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐组件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
5.Switch路由组件的使用
import React from 'react'
import { BrowserRouter as Router, Route, Link, NavLink, Switch } from 'react-router-dom'
import './app.css'
export default class App extends React.Component {
render() {
return (
<Router>
<NavLink to='/home'>去到首页</NavLink>
<br />
<NavLink to='/my' >去到我的页面</NavLink>
<br />
<NavLink to='/friend' >去到朋友页面</NavLink>
<div className='router'>
{/* 使用路由时都要放在switch标签里 */}
<Switch>
{/* switch可以解决匹配多个页面路由的问题,当第一个组件路径匹配上时就不继续往下匹配(这样就和vue一样了) */}
<Route path="/home" exact component={Home}></Route>
<Route path="/home" exact component={Home}></Route>
<Route path="/home" exact component={Home}></Route>
<Route path="/home" exact component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
{/* 在Switch最后写上没有路径的组件,设置404页面 */}
<Route component={NotFound}></Route>
</Switch>
</div>
</Router>
)
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <h1>我是我的音乐组件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
function NotFound() {
return <h1>404NotFound</h1>;
}
6.嵌套路由
import React from 'react'
import { BrowserRouter as Router, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom'
import './app.css'
export default class App extends React.Component {
render() {
return (
<Router>
<NavLink to='/home'>去到首页</NavLink>
<br />
<NavLink to='/my' >去到我的页面</NavLink>
<br />
<NavLink to='/friend' >去到朋友页面</NavLink>
<div className='router'>
<Switch>
{/* 重定向到首页,和exact一起使用 */}
<Redirect from='/' to='/home' exact></Redirect>
<Route path="/home" exact component={Home}></Route>
{/* 作为父级路由时,不能加exact属性,否则会显示不出子路由 */}
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
<Route component={NotFound}></Route>
</Switch>
</div>
</Router>
)
}
}
function Home() {
return <h1>我是首页组件</h1>;
}
function MyMusic() {
return <>
<h1>我是我的音乐组件</h1>
<Link to='/my/one'>跳到我的音乐1</Link>
<br />
<Link to='/my/two'>跳到我的音乐2</Link>
<br />
<Link to='/my/three'>跳到我的音乐3</Link>
<br />
<Switch>
{/* 子路由路径可以与父路由相同,当跳转到父路由页面时,这个子路由也一起显示 */}
{/* <Route path="/my" exact component={Myone}></Route> */}
{/* 子路由的路径要写全 */}
<Route path="/my/one" exact component={Myone}></Route>
<Route path="/my/two" component={Mytwo}></Route>
<Route path="/my/three" component={Mythree}></Route>
</Switch>
</>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
function NotFound() {
return <h1>404NotFound</h1>;
}
function Myone() {
return <h2>我的音乐组件-子组件1</h2>;
}
function Mytwo() {
return <h2>我的音乐组件-子组件2</h2>;
}
function Mythree() {
return <h2>我的音乐组件-子组件3</h2>;
}
7.编程式导航
import React from 'react'
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'
export default class App extends React.Component {
render() {
return (
<>
<Router>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
</Switch>
</Router>
</>
)
}
}
function Home({ history }) {
return <>
{/* 注意,只有设置过路径的组件才有history对象 */}
{/* 函数式组件props上存在history方法,使用history.push可以跳转到任意页面,history.go 和 history.goBack用法和vue一致 */}
{/* 如果是class组件就使用this.props.history去获取方法 */}
<button onClick={() => history.push('/my')}>跳转到my页</button>
<h1>我是首页组件</h1>
</>
}
function MyMusic() {
return <h1>我是我的音乐组件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}
8.路由传参
import React from 'react'
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'
export default class App extends React.Component {
render() {
return (
<>
<Router>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/my/:id" component={MyMusic}></Route>
<Route path="/friend" component={Friend}></Route>
</Switch>
</Router>
</>
)
}
}
function Home({ history }) {
return <>
<button onClick={() => history.push('/my')}>跳转到my页</button>
<h1>我是首页组件</h1>
</>
}
function MyMusic({ match: { params: { id } } }) {
console.log(id);
return <h1>我是我的音乐组件</h1>;
}
function Friend() {
return <h1>我是朋友组件</h1>;
}