react学习笔记[js的库]
库是基于js的扩展并没有脱离js, 框架是脱离了原生的环境
安装脚手架:
npm i create-react-app -g
启动
create-react-app 项目名
特点: 组件化 跨平台 声明式开发[数据驱动]
react核心架构
1、入口文件: index.js
2、
react:核心库
react-dom: 类似于vue中的app, 将组件的 虚拟dom节点渲染到index.html中
react-script: 隐藏配置文件
react支持插件
1、 在设置搜索emmet:在【Include Languages】 中添加jsx支持
2、 安装:ES7+ React/Redux/React-Native snippets v4.4.3 插件
JSX (xml in js)
注意:jsx中的标签都不是真是dom,可以看作标签时一个一个对象
jsx的基本语法:
1、jsx环境会将 {} 中的代码按照js来解析, 而且最终会编译成dom 所以js只能写 表达式
const msg = 'hello react'
root.render(
<div>
{/* 这是注释 */}
{msg}
</div>
);
2、 jsx中的标签属性 和js关键字冲突 转义成其他属性, 也可以通过解构添加属性
const attr = {
a: 'a',
b: 'b'
}
root.render(
<div>
<label htmlFor="b" className='xx'>
点我点我
<input type="checkbox" name="" id="b" />
<button {...attr}>看我属性</button>
</label>
</div>
);
3、 jsx只能放在一个根标签中[仅能有一个根标签]
jsx的原理
在js中写的jsx标签, react在运行之前会自动编译 过程: 分析标签 -> 调用React.createElement() [相当于vue中的h渲染函数] -> 编译成虚拟dom对象
<div className="box" id="container">
<p className="op">这是p</p>
<span>这是span</span>
这是文本
</div>
// 运行时会自动编译成如下代码
React.createElement('div', {className: 'box', id:'container'}, [
React.createElement('p', {className: 'op'}, ['这是p']),
React.createElement('span', {}, ['这是span']),
'这是文本'
])
一、react两种组件 [ 函数式组件、class组件 ]
1、 函数式组件:[当作组件使用时首字母必须是大写的]
定义函数式组件
const App = (props) => {
console.log(props);
return (
<div>
<h1>我是函数式组件</h1>
{props.title}
</div>
)
}
使用函数式组件, 通过自定义属性传递props, 函数的第一个参数就是props
root.render(
<App title="hello react"/>
);
2、class组件
应掌握 class的属性、方法、继承、静态属性、this指向问题 tips:在使用jsx时, 相当于将class实例化并且调用了render函数。可以通过自定义属性传递参数,参数会挂载在实例上的props中 创建一个class组件
import React, { Component } from 'react'
class Index extends Component {
render() {
console.log(this);
return (
<div>
<h1>
hello world
</h1>
{this.props.title}
</div>
)
}
}
export default Index
使用class组件
root.render(
<Index title="hello react"/>
);
3、class组件与 函数式组件对比
1、 没有内部状态 2、 没有生命周期钩子
二、 react的组件样式
1、行内样式 (不推荐)
jsx中的样式需要给一个对象, 连接符去‘-’变大驼峰、 可以省略px单位
<h1 style={{ color:'red', fontSize: '20' }}>
hello world
</h1>
2、组件引入外部样式 (不推荐)
支持文件: css 、 scss、 less文件, 注意:除了css、其他样式均需要安装依赖
// 引入外部css
import './css/style.css'
import './scss/style.scss'
问题: 引入外部文件会影响到全局
3、css模块 (推荐使用)
1、 命名: name.module.css [name对应组件名字]
h1{
background-color: yellow;
}
.head{
color: rgb(7, 32, 160);
}
.box{
width: 200px;
height: 200px;
background-color: pink;
:global{
.p{
background-color: blue;
}
}
}
2、使用
// 引入外部css
import styles from './css/TodoList.module.css'
import styles2 from './scss/TodoLists.module.scss'
console.log(styles);
console.log(styles2);
class Index extends Component {
render() {
return (
<div>
<h1 className={styles.head}>
hello world
</h1>
{/* <h1>111</h1> */}
<div className={styles2.box}>
<p className='p'>
{this.props.title}
</p>
</div>
</div>
)
}
}
原理: 将原选择器重命名带上一段哈希值保证不会重名
3、总结:
- a、注意: 标签选择器不会重新编译、还是会影响全局
- b、嵌套的层级只要将外层重写,内部用global声明不要重写
4、使用插件 [style-components]
三、props校验 [prop-types]
安装三方库
npm install --save prop-types
使用
import React from 'react';
import PropTypes from 'prop-types';
// 类组件
class MyComponent extends React.Component {
render() {
}
}
// 函数式组件
function MyComponent(props) {
}
// 两个组件均可以这样使用
MyComponent.propTypes = {
// 基础校验规则
optionalArray: PropTypes.array,
optionalBigInt: PropTypes.bigint,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// A React element (ie. <MyComponent />).
// 直接 { props.xx 即可渲染 }
optionalElement: PropTypes.element,
// A React element type (eg. MyComponent).
// 直接 <props.xx> 渲染
optionalElementType: PropTypes.elementType,
// 必须是message的实例
optionalMessage: PropTypes.instanceOf(Message),
// 枚举, 必须是其中之一
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 是多个类型中的一个
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 是某个固定类型数组
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 对象属性必须是某个类型
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 描述对象结构,仅仅要求其中一部分
optionalObjectWithShape: PropTypes.shape({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 描述结构,要求必须和结构一样
optionalObjectWithStrictShape: PropTypes.exact({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 链式调用
requiredFunc: PropTypes.func.isRequired,
// 任意类型
requiredAny: PropTypes.any.isRequired,
};
props的默认值
若父组件没有传递则使用默认值,传递则使用传递的
// 定义静态属性defaultProps即可
TodoHead.defaultProps = {
d: 'd的默认值'
}
props的children [可以类比vue中的插槽]
父组件可以在 子组件的双标签 中嵌入模板
<Todo
a={
<h1> 我是app中的a </h1>
}
>
<p>这是p</p>
<p>这是后面的p</p>
</Todo>
子组件内部可以直接渲染
<div> Todo组件
{this.props.a}
{this.props.children}
</div>
# 四、class组件的state
- 问题:直接修改state视图不会刷新, 因为react不是mvvm架构
- 1、 使用this.forceUpdate() [不推荐]
- 2、 使用setState() [推荐]
函数组件管理状态的属性, 应挂载在实例上.
使用、修改state
修改state使用setState({} , () => {})
import React, { Component } from 'react'
export default class Todo extends Component {
// state应挂载在实例
state = {
a: 10,
b: true,
c: [1,2,3,4]
}
// 若要传参使用construct, this.state 来定义
/* construct(){
this.state={
}
} */
change = () => {
// 修改是个异步的, 可以同时修改多个
this.setState({
a: '值修改了',
c: [1,1,1,1]
})
console.log(this.state.a);
}
render() {
return (
<div>
<h1>todo 组件</h1>
<button onClick={
this.change
}>修改状态</button>
{this.state.a}
<ul>
{
{/* 访问state数据 */}
this.state.c.map((item, index) => {
{/* 遍历需要传递一个key */}
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}
}
2、获取异步修改后的数据[18版本以前在原生事件中同步,现在都是异步]
setState中第二个参数是回调函数用于获取异步修改后最新的数据
change = () => {
// 修改是个异步的, 可以同时修改多个
this.setState({
a: '值修改了',
c: [1,1,1,1]
},
// 回调, 在数据更新视图刷新完成后触发
() => {console.log(this.state.a,222);})
}
五、核心概念
1、单向数据流 (不可以修改props)
2、状态提升 (应该明确区分 ui组件 逻辑组件 将状态提升到逻辑组件[父组件]) ---> 内部的弹窗、表单等 状态在子组件处理,通过自定义事件传递给父组件处理业务
3、受控组件/非受控组件
六、react的和合成事件
合成事件: 集成原生事件、绑定 react 方法
语法: on+原生事件 [onClick/onChange...]
<button onClick={事件函数}>点击</button>
1、在class组件中如何定义 事件函数 [主要考虑this指向问题]
- 1、在行内直接用箭头函数 [业务代码嵌入了视图,不推荐使用]
import React, { Component } from 'react'
export default class Todo extends Component {
// state应挂载在实例
state = {
msg: 'hello react'
}
render() {
return (
<div>
<h1>todo 组件</h1>
<button onClick={
// 行内定义箭头函数
() => { this.setState({ msg: '值修改了' })}
}>修改状态</button>
{this.state.msg}
</div>
)
}
}
- 2、原型上定义方法,在行内通过bind改变this指向 [render触发会导致bind频繁返回新函数,不推荐]
changeMsg(){
this.setState({ msg: '值修改了' })
}
render() {
return (
<div>
<h1>todo 组件</h1>
<button onClick={
this.changeMsg.bind(this)
}>修改状态</button>
{this.state.msg}
</div>
)
}
- 3、在construct上修改this [只会触发一次bind、但是有两个方法、绑定的是bind返回的函数,不推荐]
export default class Todo extends Component {
constructor(){
super()
this.state = {
msg: 'hello react'
}
this.changeMsg = this.changeMsg.bind(this)
}
changeMsg(){
this.setState({
msg: '值改变了'
})
}
render() {
return (
<div>
<h1>todo 组件</h1>
<button onClick={
this.changeMsg
}>修改状态</button>
{this.state.msg}
</div>
)
}
}
- 最推荐:变量=箭头函数形式 这种会将changeMsg挂载在实例上面并且this永远指向实例
import React, { Component } from 'react'
export default class Todo extends Component {
state = {
msg : 'hello react'
}
changeState = () => {
this.setState({
msg: '值改变了'
})
}
render() {
return (
<div>
<h1>todo 组件</h1>
<button onClick={
this.changeState
}>修改状态</button>
{this.state.msg}
</div>
)
}
}
七、事件对象
1、 事件函数的第一个参数就是事件对
e.target: 获取事件dom
e.stopPropagation(); :阻止冒泡
e.preventDefault(); :阻止默认事件
2、 事件函数传参
将箭头函数作为事件函数、将fn在箭头函数中调用即可实现传参
export default class Todo extends Component {
render() {
return (
<div className='box1' onClick={this.fn1}>
1
<div className='box2' onClick={this.fn2}>
2
<div className='box3' onClick={
(e) => {
this.fn3(e,3)
}
}>
3
</div>
</div>
</div>
)
}
fn3 = (e, a) => {
console.log(3);
// 组织冒泡
e.stopPropagation();
console.log(a);
}
八、react渲染视图
理解jsx语法、标签就是对象
1、 条件渲染
使用三目运算符
<>
<button onClick={() => {
this.setState({
isShow: !this.state.isShow
})
}}>{this.state.isShow ? '隱藏' : '显示'}</button>
{
this.state.isShow ? <h1 style={{textAlign: 'center'}}>Todo</h1> : null
}
</>
2、循环渲染
给每一项处理加上标签渲染即可, 注意:循环需要一个独一无二的key
import React, { Component } from 'react'
export default class Todo extends Component {
state = {
arr: ['a','b','c','d']
}
render() {
return (
<>
{
this.state.arr.map((item,index) => {
return <li key={index}>{item}</li>
})
}
</>
)
}
}
3、富文本渲染
使用react提供的属性
import React, { Component } from 'react'
export default class Todo extends Component {
state = {
context: '<h1 style="color:red">富文本数据</h1>'
}
render() {
return (
<>
{
this.state.context
}
<div dangerouslySetInnerHTML={{__html: this.state.context}}></div>
</>
)
}
}
九、容器组件[Fragment / 或者<></> 空标签]
主要用于解决jsx自动生成的dom嵌套太深的问题
import React, { Component, Fragment } from 'react'
export default class Todo extends Component {
render() {
return (
<Fragment>
<h1>我是h1</h1>
</Fragment>
/* <>
<h1>我是h1</h1>
</> */
)
}
}
十、表单值的绑定
1、单向绑定 [使用defaultValue]
import React, { Component } from 'react'
export default class Todo extends Component {
state = {
msg: 'hello react',
isChecked: true
}
render() {
return (
<>
<input type="text" defaultValue={this.state.msg} />
<hr />
{this.state.msg}
<hr />
<input type="checkbox" defaultChecked={this.state.isChecked}/> {this.state.isChecked ? '差勤' : '正忙'}
</>
)
}
}
2、双向绑定 [使用value、合成事件]
import React, { Component } from 'react'
export default class Todo extends Component {
state = {
msg: 'hello react',
isChecked: true
}
render() {
return (
<>
<input type="text" value={this.state.msg} onChange={this.changeMsg}/>
<hr />
{this.state.msg}
<hr />
<input type="checkbox" checked={this.state.isChecked} onChange={this.changeChecked}/>
{this.state.isChecked ? '选中' : '未选'}
</>
)
}
changeMsg = (e) => {
this.setState({
msg: e.target.value
}, () => {console.log(this.state.msg);})
}
changeChecked = (e) => {
this.setState({
isChecked: e.target.checked
}, ()=>{console.log(this.state.isChecked);});
}
}
十一、react的组件通信
1、父子组件通信
1、 父向子通信:通过自定义属性 在props中接收 父组件
export default class App extends Component {
state = {
A_msg: '我是父组件数据'
}
render() {
return (
<>
<Todo a={this.state.A_msg}>
</Todo>
</>
)
}
}
子组件
export default class Todo extends Component {
render() {
return (
<>
子组件:{ this.props.a }
</>
)
}
}
2、 子向父通信:父组件定义函数 props接受父组件传递来的函数, 在事件中触发并且传递参数 父组件
export default class App extends Component {
state = {
msg: ''
}
biubiu = (msg) => {
console.log('我收到了子组件的消息'+msg);
this.setState({
msg: msg
})
}
render() {
return (
<>
父组件:{ this.state.msg }
<hr />
<Todo emit={this.biubiu}>
</Todo>
</>
)
}
}
子组件
export default class Todo extends Component {
state = {
msg: '我是子组件消息'
}
render() {
return (
<>
<h1>我是todo组件</h1>
<button onClick={() => {
this.props.emit(this.state.msg)
}} >点击向父父组件发送信息</button>
{ this.props.a }
</>
)
}
}
Todo.propTypes = {
emit: PropTypes.func.isRequired
}
2、兄弟组件通信 [使用PubSub三方插件,原理发布订阅]
npm i PubSub -D
itemA
import pubsub from './utils/PubSub'
export default class itemA extends Component {
state = {
msg : 'this is A msg'
}
render() {
return (
<>
<div>itemA</div>
<button onClick={() => {pubsub.publish('sendMsg', this.state.msg)}}>点击向B发送数据</button>
</>
)
}
}
itemB
import pubsub from './utils/PubSub'
export default class itemB extends Component {
state = {
msg: '',
}
render() {
return (
<>
<div>itemB</div>
兄弟组件的数据:{this.state.msg}
</>
)
}
componentDidMount() {
pubsub.subscribe('sendMsg', (data) => {
console.log('我收到了兄弟组件的来信:'+data);
this.setState({
msg: data
})
})
}
}
十二、 AntDesign组件库 + TodoList案例
官网 安装: 直接引入使用即可
npm install antd --save
十三、 react组件的生命周期钩子
(网址)[www.yuque.com/wangkai-jjg…]
1、 初始化挂载阶段
- constructor [类实例化时会调用]
- static getDerivedStateFromProps
- render
- componentDidMount 组件会更新的三个场景:props改变、setState调用、forceUpdate调用
2、 更新阶段
- static getDerivedStateFromProps
- shouldComponentUpdate ==> 可以阻止视图更新
- render
- componentDidUpdate
3、 卸载阶段
- componentWillUnmount
生命周期应用场景
常用钩子
1、初始化阶段
- constructor: 定义 state 给实例初始化
- componentDidMount: 用于调用组件初始化函数, 获取dom
2、更新阶段
- componentDidUpdate:可以获取更新后的dom、不推荐使用
3、卸载阶段
- componentWillUnmount: 用于卸载组件中的全局时间和属性
父组件
export default class App extends Component {
state = {
isShow: true,
}
render() {
return (
<>
<button onClick={() => {this.setState({isShow: !this.state.isShow})}}>
{this.state.isShow ? "隐藏" : "显示"}
</button>
{
this.state.isShow
&&
<Todo />
}
</>
)
}
}
子组件
export default class Todo extends Component {
constructor() {
super();
this.state = {
todo: [],
timer: -1
};
}
render() {
return (
<>
<h1>todo</h1>
<div style={{height: 2000}}>
{this.state.todo.map((item) => {
return <div key={item.id}>{item.name}</div>;
})}
</div>
</>
);
}
fetchCates = () => {
axios.get("https://api.it120.cc/conner/cms/category/list").then ((res) => {
console.log(res);
if (res.data.code === 0) {
console.log(res);
this.setState({
todo: res.data.data,
});
}
});
};
componentDidMount () {
this.fetchCates();
window.onscroll = () => {
console.log('滚动了');
}
const timer = setInterval(()=>{
console.log('战斗!');
},2000)
this.setState({
timer
})
}
componentWillUnmount () {
console.log('组件卸载!');
window.onscroll = null;
clearInterval(this.state.timer)
}
}
进阶钩子
- static getDerivedStateFromProps 始终在render前触发, 返回对象的属性会自动添加到组件的state上
export default class Todo extends Component {
state = {
msg: 'hello',
}
static getDerivedStateFromProps(props, state) {
/*
state: 组件的 默认state
props:组件的 props
返回对象的属性会自动添加到组件的state上
*/
return {
doubleNum : props.num * 2,
reversMsg: state.msg.split('').reverse().join('')
}
}
render() {
console.log(this.state);
return (
<div>Todo</div>
)
}
}
十四、react中的组件优化
1、react 组件更新机制
祖先组件更新,所有的后代组件都会无条件的随之更新。导致后代组件无意义的re-render
2、组件的更新优化方案
- 1、使用shouldComponentUpdate钩子阻止后代组件更新 [不推荐]
shouldComponentUpdate(nextProps, nextState){
/*
参数1: 如果更新 更新后最新的props 、 this.props是更新前的props
参数2: 如果更新 更新后最新的state 、 this.state是更新前的state
*/
// 判断子组件数据发生改变没
return nextProps.item !== this.props.item
}
- 2、让类组件继承PureComponent[推荐]
十五、 react获取dom实例
引入createRef 获取子组件实例 或者 dom实例[在类组件中、将dom对象挂载在组件实例]
import React, { Component, createRef } from 'react'
export default class Todo extends Component {
constructor(){
super()
this.btn = createRef(null)
this.item = createRef(null)
}
render() {
return (
<>
<div ref={this.item}>Todo</div>
<button ref={this.btn}>按钮</button>
</>
)
}
componentDidMount(){
console.log(this.btn.current);
console.log(this.item);
}
}
十六、 context 上下文
实现了 不需要通过祖先组件层层传递props就可以 跨组件通信
1、 context 的基础使用
1、 引入创建一个context
import { createContext } from "react";
// 实例化
const context = createContext();
/*
context对象下有两个属性 (组件)
Provider 数据提供
通过value属性提供数据, 数据只能由后代组件 通过Consumer获取
Consumer 数据消解
*/
const { Provider, Consumer } = context;
export {
Provider,
Consumer,
context
}
2、 让Provider 作为根节点、 使用value提供数据
import { Provider } from './context/index'
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
const data = {
a: 10,
b: 20
}
root.render(
<Provider value={data}>
<App />
</Provider>
);
3、 使用Customer 接收数据
import React, { Component } from 'react'
import { Consumer } from '../context'
export default class A extends Component {
render() {
return (
<Consumer>
{
(data) => {
console.log(data.a);
return <div>{data.a}<h2>A组件</h2></div>
}
}
</Consumer>
)
}
}
2、 context 的进阶使用 [对Provider提供的数据修改、并且刷新试图]
1、 将context的Provider放在一个组件中 context
import React, { Component, createContext } from 'react'
const context = createContext()
const { Provider, Consumer } = context;
class ContextProvider extends Component {
state = {
a: 10,
b: 20
}
render() {
return (
<Provider value={{ ...this.state, changea: this.changea }}>
{this.props.children}
</Provider>
)
}
changea = (n) => {
this.setState({
a: n
}, () => { console.log(this.state); })
}
}
export {
Consumer,
ContextProvider,
context
}
2、 将App使用ContextProvider包裹、使用this.props.children嵌入Provider中 入口
import {ContextProvider} from './context'
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ContextProvider>
<App />
</ContextProvider>
);
3、在后代组件接收并且调用根组件传递的数据函数 A.jsx
import React, { Component } from "react";
import { Consumer } from "../context";
export default class A extends Component {
render() {
return (
<Consumer>
{(data) => {
console.log(data.a);
return (
<div>
<button onClick={() => {data.changea(101)}}>修改a</button>
{data.a}
<h2>A组件</h2>
</div>
);
}}
</Consumer>
);
}
}
十七、 高阶组件 [hoc] (high order component)
补充:hook函数 => 必须以useXxx命名, 在函数组件内部使用。
原理 :柯里化函数 [最外层的有名字的函数称为高阶函数] 本质就是一个高阶函数, 返回一个组件, 接收参数(也是一个组件)。 返回一个新的被修饰后的组件[可以给被修饰的组件加其他组件和props]。
注意:高阶组件以withXxx命名,
fn()()
定义:
import React from 'react'
const withComponent = (Com) => {
return (props) => {
return(
<>
<h2>i am header</h2>
<Com {...props}/>
<h2>i am footer</h2>
</>
)
}
}
export default withComponent
使用
import React from 'react'
export default function A(props) {
console.log(props);
return (
<div>A</div>
)
}
---------函数式组件---------
十八、 react的一些hook函数
[16.8版本]:推出了 react-hooks 目的是解决函数式组件的缺陷
要求: 1、命名要求 ==> 必须以useXxx开头 2、必须在函数式组件内部使用
1、 useState
语法: let [值(名称), 修改值的方法] = useState(初始值) 作用: 用于将数据更改后使视图刷新 注意: 该更改为异步操作无法直接获取到更改后的值!. 使用:
import React from 'react'
import { useState } from 'react'
export default function App() {
let [a, setA] = useState(10)
const addNum = () => {
setA(++a)
console.log(a);
}
let [obj, setObj] = useState({b: 20})
const changeObj = () => {
obj.b = 30
setObj(obj)
console.log(obj.b);
}
return (
<div>
{a}
<button onClick={addNum}>+</button>
<hr />
{obj.b}
<button onClick={changeObj}>修改</button>
</div>
)
}
2、 useEffect
语法: useEffect(() => {} , [依赖])
作用:
1、 若直接写回调、相当于 componentDidMount 和 componentDidUpdate 触发时机, 但是在此钩子没法调用useState, 否则死循环。
useEffect(() => {})
2、 给一个依赖, 当依赖改变才会触更新, 可以获取useState的set修改后数据和dom。 相当于立即侦听的侦听器。
useEffect(() => {} , [依赖])
3、 模拟生命周期的钩子:将依赖设置为一个不会更改的值 比如:空
useEffect(() => {} , [])
4、 模拟卸载前的钩子:
useEffect(() => {
return () => {
// 卸载前的钩子
}
} , [])
注意: 还有个相同作用的useLayoutEffect, 知道即可,一般不使用
3、useContext
1、 引入创建一个context
import { createContext } from "react";
// 实例化
const context = createContext();
/*
context对象下有两个属性 (组件)
Provider 数据提供
通过value属性提供数据, 数据只能由后代组件 通过Consumer获取
Consumer 数据消解
*/
const { Provider, Consumer } = context;
export {
Provider,
Consumer,
context
}
2、 让Provider 作为根节点、 使用value提供数据
import { Provider } from './context/index'
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
const data = {
a: 10,
b: 20
}
root.render(
<Provider value={data}>
<App />
</Provider>
);
3、接收使用
import React, { useContext } from 'react'
import { context } from "./context"
export default function App() {
const data = useContext(context)
return (
<div>{data.a}</div>
)
}
4、useMemo [类似计算属性]
作用: 根据依赖 进行计算 得到新的状态, 多次使用或者组件刷新不会重新调用、只有以来改变才会重新调用
import React, { useMemo, useState } from 'react'
export default function App() {
let [num1 , setNum1] = useState(10)
let [num2 , setNum2] = useState(20)
const doubleNum = useMemo(() => {
console.log('触发了');
return num1 * 2
}, [num1])
return (
<div>
{num1} | {doubleNum} {doubleNum}
<button onClick={() => {setNum1(num1+1)}}>+</button>
<hr />
{num2}
<button onClick={() => {setNum2(num2+1)}}>+</button>
</div>
)
}
5、memo高阶组件
作用: 解决子组件跟随父组件无意义的刷新问题. 只有当父组件传递的props变化才更新. 父组件
import React, { useMemo, useState } from 'react'
import Children from './children'
export default function App() {
let [num1 , setNum1] = useState(10)
let [num2 , setNum2] = useState(20)
const doubleNum = useMemo(() => {
console.log('触发了');
return num1 * 2
}, [num1])
return (
<div>
{num1} | {doubleNum} {doubleNum}
<button onClick={() => {setNum1(num1+1)}}>+</button>
<hr />
{num2}
<button onClick={() => {setNum2(num2+1)}}>+</button>
<hr />
<Children num2 = {num2}/>
</div>
)
}
子组件
import React, { memo } from 'react'
export default memo (function children(props) {
console.log('子组件触发');
return (
<div>children {props.num2}</div>
)
})
6、useCallback
作用: 用于避免某个函数多此重新触发, 依赖不改变不重新触发。
const addArr = useCallback( () => {
setArr([...arr, arr.length+1])
}, [arr])
7、useRef
作用: 1、获取自身的dom标签 2、获取class子组件的实例 3、获取函数式子组件的内部dom标签 [需要配合forwardRef高阶组件使用] 父组件
import React, { useRef, useEffect } from "react";
import Children from "./children";
import Children2 from "./children2";
export default function App() {
const btn = useRef(null);
const children2 = useRef(null)
const children = useRef(null)
// 初始胡钩子
useEffect(() => {
console.log(btn.current);
console.log(children2.current);
console.log(children.current);
}, [])
return (
<div>
<button ref={btn}>按钮</button>
{/* 函数式组件 */}
<Children ref={children}/>
{/* 类组件 */}
<Children2 ref={children2}/>
</div>
);
}
class子组件
import React, { Component } from 'react'
export default class children2 extends Component {
state = {
a:1
}
render() {
return (
<div>children2</div>
)
}
}
函数式组件
import React from 'react'
import { forwardRef } from 'react'
// ref传递的属性当作第二个参数, 转发传递给内部的dom
export default forwardRef(function children(props, children) {
return (
<div ref={children}>children</div>
)
})
十八、react的路由
(6.x)[reactrouter.com/en/6.15.0] => 全面拥抱函数式组件 万物皆组件: 安装:
react-router // 核心语法包
react-router-dom // b/s 浏览器端
react-router-native // c/s
npm i react-router-dom -S
1、路由模式:
1、 HashRouter 2、 BrowserRouter
2、基础语法:
注意: 必须将路由包裹根组件才可以生效
import { HashRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<HashRouter>
<App />
</HashRouter>
);
定义路由组件:[注意:Route必须包含在Routes中]
import {Route , Routes} from 'react-router-dom'
import Home from './pages/Home'
import NewsPage from './pages/NewsPage'
import NotFound from './pages/NotFound'
export default function App() {
return (
<>
<Routes>
<Route path='/' element={<Home/>}></Route>
<Route path='/news' element={<NewsPage/>}></Route>
<Route path='/404' element={<NotFound/>}></Route>
</Routes>
</>
)
}
3、路由导航组件
1、 link
to: 定义跳转的路径
- 可以是 '字符串' 、 对象 { pathname: '/' , }
replace: 布尔值、跳转会覆盖历史记录
state: 用于跳转传参
缺点: 没有高亮样式处理
<Link to="/">首页</Link>
<Link to={{pathname : '/news'}}>新闻</Link>
<Link to="/about" replace>我们</Link>
2、NavLink [了解即可] 作用: 在Link的基础上、增加了高量样式
- 默认有高亮类
- 可以通过回调函数自定义高亮类
- 可以使用插槽来自定义导航渲染的标签
<NavLink to="/">首页</NavLink>
<NavLink to={{pathname : '/news'}}>新闻</NavLink>
<NavLink to="/about" replace>
<button>我们</button>
</NavLink>
4、路由重定向[Navigate] 和 404
1、Navigate: 出现就会跳转、所以需要一个条件
to:跳转路径
replace:覆盖跳转记录
<Routes>
<Route path='/home' element={<Home/>}></Route>
<Route path='/news' element={<NewsPage/>}></Route>
<Route path='/about' element={<NotFound/>}></Route>
<Route path='/' element={<Navigate to="/home" />}></Route>
</Routes>
2、404 在路由中 * 的优先级最低
<Route path='/about' element={<NotFound/>}></Route>
## 5、嵌套路由 总结:
- 1、子路由的Route 可以直接嵌套写在父路由的Route中, 且可以简写
- 2、子路由的导航可以在父路由的组件中定义、也可以简写、并且为子路由的出口 主路由
import React from 'react'
import {Route , Routes, Link, NavLink, Navigate} from 'react-router-dom'
import Home from './pages/Home'
import NewsPage from './pages/NewsPage'
import About from './pages/About'
import NotFound from './pages/NotFount'
import Native from './pages/NewsPage/components/Native'
import Abroad from './pages/NewsPage/components/Abroad'
export default function App() {
return (
<>
<NavLink to="/">首页</NavLink>
<NavLink to={{pathname : '/news'}}>新闻</NavLink>
<NavLink to="/about" replace>
<button>我们</button>
</NavLink>
<Routes>
<Route path='/home' element={<Home/>}></Route>
<Route path='/news' element={<NewsPage/>}>
{/* 可以不带 / 简写, react会自动不全 */}
<Route path='native' element={<Native/>}>本地新闻</Route>
<Route path='/news/abroad' element={<Abroad/>}>本地新闻</Route>
<Route path='/news' element={<Navigate to="/news/native" replace/>}></Route>
</Route>
<Route path='/about' element={<About/>}></Route>
<Route path='*' element={<NotFound/>}></Route>
<Route path='/' element={<Navigate to="/home" />}></Route>
</Routes>
</>
)
}
子路由
import React from 'react'
import { Outlet, Link } from 'react-router-dom'
export default function NewsPage() {
return (
<>
<h1>新闻页</h1>
<Link to="abroad"> 海外 </Link>
<Link to="native"> 本地 </Link>
<hr />
{/* 这是子路由的出口组件 */}
<Outlet />
</>
)
}
6、react-router 常见的hook函数 [编程式导航]
1、 useLocation 作用: 获取当前路由的静态信息对象
const location = useLocation();
2、useNavigate 作用: 跳转路由、路由传参
const navigate = useNavigate();
// 操作历史记录
<button onClick={() => navigate(0)}>刷新</button>
<br />
// 跳转路由
<button onClick={() => navigate('/home')}>首页</button>
// 覆盖跳转
<button onClick={() => navigate('/news')} replace>新闻页</button>
// 跳转传参
<button onClick={() => navigate('/about', {state: {a:10, b:20}})}>关于页</button>
7、路由跳转传参
1、 动态路由传参 特点:显示传参、刷新不丢失 定义:
<Route path='/about/:id' element={<About/>}></Route>
跳转:
<button onClick={() => navigate('/about/10086', {state: {a:10, b:20}})}>关于页</button>
获取:使用[useParams 钩子]
const param = useParams()
console.log(param.id);
2、state 传参 注意:若state传递为空、react会默认为null 特点:隐式传参、数据刷新不丢失 跳转:
// 编程式跳转
<button onClick={() => navigate('/about', {state: {a:10, b:20}})}>关于页</button>
// 导航组件
<Link to="/about" state={{a:10, b: 0}}></Link>
获取:使用[useParams 钩子]
// 编程式 、 导航式
const location = useLocation()
console.log(location.state);
3、search传参 跳转:
<button onClick={() => navigate('/home?page=10&pageSize=7')}>首页</button>
获取:使用[useSearchParams 钩子]
// 编程式 、 导航式
import React from "react";
import { useSearchParams } from "react-router-dom";
export default function Home() {
const [searchParams, setSearchParams] = useSearchParams();
console.log(searchParams.get("page"));
return (
<>
<h1>首页</h1>
<button onClick={() => {setSearchParams({page:1, pageSize:20})}}>动态设置参数</button>
</>
);
}
8、useRoutes、配置型路由
1、新建一个routes文件夹、将路由配置定义在其中的文件内并导出
/* eslint-disable import/no-anonymous-default-export */
import Home from "../pages/Home";
import NewsPage from "../pages/NewsPage";
import About from "../pages/About";
import NotFound from "../pages/NotFount";
import Native from "../pages/NewsPage/components/Native";
import Abroad from "../pages/NewsPage/components/Abroad";
import { element } from "prop-types";
export default [
{
path: "/home",
element: <Home />,
},
{
path: "/news",
children: [
{
path: 'native',
element: <Native/>
},
{
path: 'abroad',
element: <Abroad/>
}
],
element: <NewsPage />,
},
{
path: "/about",
element: <About />,
},
{
path: "*",
element: <NotFound />,
}
]
2、使用 引入useRoutes 、 传入routes的配置文件即可
import React from 'react'
import { Link, useNavigate, useRoutes} from 'react-router-dom'
import routesConfig from './routes'
export default function App() {
const navigate = useNavigate()
const routes = useRoutes(routesConfig)
return (
<>
<button onClick={() => navigate(0)}>刷新</button>
<br />
<button onClick={() => navigate('/home?page=10&pageSize=7')}>首页</button>
<button onClick={() => navigate('/news')}>新闻页</button>
{/* <button onClick={() => navigate('/about/10086', {state: {a:10, b:20}})}>关于页</button> */}
<Link to="/about" state={{a:101, b:10}}>
<button>关于</button>
</Link>
{
routes
}
</>
)
}