1. React 基础语法
0 .安装
- 直接创建 React
npx create-react-app my-app
- 通过 Vite 创建 React
# npm 6.x
npm init vite@latest my-react --template react
# npm 7+, 需要额外的双横线:
npm init vite@latest my-vue-app -- --template react
# yarn
yarn create vite my-react --template react
# pnpm
pnpm create vite
1.React的开始
- ReactDOM.rander(element,container,callback)
ReactDOM.render(
// 虚拟DOM
<h1>我是react</h1>,
// 插入元素容器
document.querySelector('#app'),
// 执行完成回调函数
function () {
console.log('我ok了');
}
);
2. 元素渲染
1.元素是构成 React 应用的最小单位
注意事项: 定义 React的
虚拟DOM
如果加“”则是文本渲染,标签不会渲染
- 面试:ReactDOM.render()方法有几个参数?
- 答题:3个 1.虚拟DOM 2.渲染的容器 3.回调函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aSzcxRAI-1660141981379)(E:\学习素材\Wed\Web-Train\note\React\assets\1658849336020.png)]
2. {} 的语法
- 注释需要 写在{} 内部
- 可以解析 `Nunber`、`String`等基本数据类型和表达式,但不支持objcet
- 面试: 什么叫JSX?
- 答: JSX 是一个看起来像 XML 的JS 语法
- 面试: JSX语法
- 1. JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 2. 它是类型安全的,在编译过程中就能发现错误。
- 3. 使用 JSX 编写模板更加简单快速。
3. 代替的标签属性
className
和htmlFor
代替 class 和的 for
- 面试: JSX语法中标签的哪2个属性不能使用
- 答: 属性 class 属性for 使用 className 和 htmlFor 代替
3. React 组件分类
1. 函数组件
注意事项:命名规范 和 js 命名规范一样 , 首字母大写
// 函数组件 没有constructor 不能定义state
function Tick() {
return (
<div>
<div>我是一个函数组件</div>
</div>
);
}
// <Tick></Tick> 使用的时候 把函数当成标签的形式使用
// React 函数组件 返回的是React 虚拟DOM元素
// 函数组件调用方式
ReactDOM.render(Tick(), document.querySelector('#app'));
ReactDOM.render(<Tick></Tick>, document.querySelector('#app'));
ReactDOM.render(<Tick />, document.querySelector('#app'));
2. 类组件
- 继承 React.Component 类
class Test extends React.Component {
render() {
return (
<div>
<div>我是一个类组件</div>
</div>
);
}
}
// 标签直接解析
ReactDOM.render(<Test />, document.querySelector('#app'));
3. 复合组件
let isOn = false;
function App() {
return <div>{isOn ? <Tick /> : <Test />}</div>;
}
ReactDOM.render(<App />, document.querySelector('#app'));
4. 状态管理
class Demo extends React.Component {
// 构造器
constructor() {
// super 写在constructor 第一行
super();
// 定义状态
this.state = {
// 相当于 vue 的 data
num: 1,
time: new Date(),
};
}
// 在类组件中 方法和方法之间不能写逗号(,)
// 渲染Dom
render() {
return (
<div>
<h1>状态管理</h1>
<div>{this.state.time.toLocaleString()}</div>
<button>按钮</button>
</div>
);
}
// 定义一个方法 更新视图
Time() {
this.setState({
time: new Date(),
});
}
// 生命周期
// 挂载后
componentDidMount() {
setInterval(() => {
this.Time();
}, 1000);
}
}
// 小程序中 this.setData React 中 this.setState
ReactDOM.render(<Demo />, document.querySelector('#app'));
5. 遍历
- react 遍历 数组使用 map 函数
注意点:
1. map 遍历的时候如果输出元素,必须需要加 return
2. map 遍历数组时,需要在变量的元素属性加 key,唯一值
this.state.arr.map((item,index) => { return(<li key={index}>{item}</li>)})
6. 条件渲染
1. 三目运算符
{ this.state.flag ? (<div/>) : (<div>) }
2. || && 运算符
{ this.state.flag && <div/> }
3. 函数
ions(num){
if(num === 1){
return <div>周一</div>
}else if(num === 2){
return <div>周一</div>
}else if(num === 3){
return <div>周一</div>
}else if(num === 4){
return <div>周一</div>
}else if(num === 5){
return <div>周一</div>
}else if(num === 6){
return <div>周一</div>
}else if(num === 7){
return <div>周一</div>
}
}
{this.ions(1)}
7. Css 样式
// 在官方 create-react-app 脚手架中不支持less 和 sass
// 1. num run eject 暴露config 文件 通过webpack.config.js 配置less sass
// 2. 通过vscode,webstorm自带的less sass插件实现
// React 样式处理 css in js 理念
return(
<div>
{/* 1. 行内样式 - style */}
<h1 style={{color:"red"}}>样式</h1>
<h2 style={this.state.style}>样式2</h2>
{/* 2. 类名 - classNmae 注意全局污染,因为react 中没有样式隔离,不像vue中可以隔离样式 */}
<h3 className='color'>样式3</h3>
{/* 3. css module 直接用 module.css , 起名一定要带上module,引入的时候要用对象形式就可以了 */}
<p className={aCss.p}>React样式处理 <span>css module</span></p>
<p className={this.state.flag ? 'kk':''}>动态</p>
</div>
)
8. 事件绑定
- 事件 on+首字母大写事件名 = {执行函数} 例如: onClick
export default class Tab extends Component {
constructor(){
super()
this.state = {}
// 改变 func 的 this 指向
this.func = this.func.bind(this)
}
func(){
// this 指向自身 react重写函数 使用指向 undf
}
func2 = ()=>{
// this 指向 class
}
}
2. 组件 (Class)
1. 受控组件
- 什么是受控组件?
- 答: input框被React组件 控制
实现步骤
1.组件的state中声明一个组件状态
2.给input 设置一个value 的state
3.给input 设置一个onChange 事件
4.将input 的 target值 更新 state
this.state = {
InpVal:""
}
rander(){
retrun (
<div>
<input type="text" value={InpVal} onChange={this.handleChangePublish} />
</div>
)
}
handleChangePublish(e)=>{
// 通过e 获取元素value值
this.setState({
InpVal:e.target.value
})
}
2. 非受控组件(操作DOM)
1. createRef
(React 17+ 的写法 官方推荐第一种 )
非受控组件是通过手动操作
dom
方式获取input 的 value,input的value不受当前React组件控制的 state中的状态控制
实现步骤
1.导入 createRef 函数
2.通过 createRef 函数 创建一个ref对象 存储到myInp 实例中
3.为 Dom 元素 添加 ref 属性, ref={myInp}
4.在事件处理函数中,通过this.myInp.current 获取当前dom元 将value值 赋值给state
import React,{Component,createRef} from 'react'
export default class Tab extends Component {
this.state = {
InpVal = ''
}
myInp = createRef()
rander(){
retrun (
<div>
<input type="text" ref={myInp} onChange={this.handleChangePublish} />
</div>
)
}
handleChangePublish = () => {
this.setState({
InpVal:this.myInp.current.value
})
}
}
2. ref = {“值”}
(非空组件 React <17 的写法 已废弃 有bug)
实现步骤
1.属性值 ref = {"值"}是字符串 获取你的Dom元素
2.执行函数 this.resf.定义的字符串 获取Dom元素
rander(){
retrun (
<div>
<input type="text" ref={"msg"} onChange={this.handleChangePublish} />
</div>
)
}
handleChangePublish = ()=> {
this.setState({
InpVal:this.refs.msg.value
})
}
3. ref ={()=>{}}
(React <17 的写法 没有废弃 )
ref ={(msg)=>{this.msg = msg }}
实现步骤
1.属性值 ref ={(msg)=>{this.msg = msg }} 是回调函数 获取你的Dom元素
2.执行函数 this.msg 获取Dom元素
rander(){
retrun (
<div>
<input type="text" ref={(msg)=>{this.msg = msg} onChange={this.handleChangePublish} />
</div>
)
}
handleChangePublish = ()=> {
this.setState({
InpVal:this.msg.value
})
}
3. 组件通信
组件是独立的单元,默认只能使用自己的状态数据
- 什么是组件通信?
- 答:为了能让各个组件直接进行互相沟通,这个过程就是组件传值
1. 父传子
1. props
实现步骤
1.父组件提供传递数据 state
2.给子组件标签 添加一个属性,他的属性值就是父组件定义的 state 数据
3.子组件通过 props 接收父组件中传过来的数据
注意:子组件是函数组件: 使用 props 接收传递数据(参数传值)
子组件是类组件: 使用this.pros 接收传递数据
// 定义父组件
export default class Fon extends Component {
constructor(){
super()
this.state = {
str:"给儿子的值"
// 可以传 string,number,boolder,function,objcet....
// 函数会携带参数 function (str){}
}
}
render(){
return(
<div>
<h2>我是父组件</h2>
{/*子组件* 定义一个属性 给子组件传值 /}
<Son text={this.state.str} />
<div/>
)
}
}
// 函数子组件
function Son({text}){
// 结构赋值获取 props对象 中的 text参数
return(
<>
<h2>我是子组件</h2>
<div>{text}</div> // 给儿子的值
</>
)
}
// 类子组件
class Son extends Component{
// 结构赋值获取 this.props对象 中的 text参数
const {text} = this.props
rander(){
retrun(
<>
<h2>我是子组件</h2>
<div>{text}</div> // 给儿子的值
</>
)
}
}
2. children
children
相当于 vue2 中的插槽 可以传(文本/标签/函数)
- children 属性是什么?
- 答: 标识该组件的子节点,只要组件内部有子节点,props中就该有属性
- 父组件
export default class Child extends Component {
constructor(){
super()
this.state = {
num:1
}
}
render(){
return (
<div>
<ChonA>
// 文本
123
// 标签
<h1>传递文本<h1>
// 函数
{this.add}
</ChonA>
</div>
)
}
add(){
// 函数体
}
}
- 子组件
function ChonA (porps){
return(
<>
{porps.children[0]} // 123
{porps.children[1]} // <h1>传递文本<h1>
{porps.children[2]} // add 函数
</>
)
}
3. createContext
Provider
包裹需要传值的子组件(孙组件也能取值)
Consumer
包裹需要传值的函数
- 1. 引入 createContext (可实现跨组件通信 父->子->子)
import React,{Component,createContext} from 'react'
- 2. 结构出 Provider,Consumer
const {Provider,Consumer} = createContext()
- 第二种类写法
const MyContext = createContext()
// MyContext.Provider MyContext.Consumer 结合使用
// 类组件 声明 static contextType = MyContext;
- 父组件
export default class Context extends Component {
state = {
msg: "疯狂星期四"
}
render(){
return(
<>
// Provider 传入的数据
<Provider value={this.state.msg}>
<h3>夸组件传值</h3>
<ContextA/>
</Provider>
</>
)
}
}
- 子组件
function ContextA() {
return (
<div>
<Component>
{value=>{return <div>value</div>}}
</Component>
<ContextB></ContextB>
</div>
)
}
- 孙 函数组件
function ContextB() {
return (
<div>
<Component>
{value=>{return <div>value</div>}}
</Component>
</div>
)
}
- 孙 类组件
class ContextB extends Component {
// 第二种写法 静态声明后---可以直接获取 this.context
static contextType = MyContext;
render() {
return (
<>
<div>ContextA</div>
<div>{this.context}</div>
<ContextB />
</>
);
}
}
2. 子传父
- 事件传值
export default class Input extends Component {
constructor(){
super()
this.state = {num:1}
}
rander(){
return(
<>
// 子组件传入自定义事件
<Son handlClick="this.handlClick" />
</>
)
}
// 父级自定义事件
handlClick(data){
this.state.num = data // 10
}
}
class Son extends Component {
constructor(){
super()
this.state = {
num:10
}
}
rander(){
return(
<>
// 事件触发 传值给父组件
<button onClick="this.props.handlClick(this.state.num)"></button>
</>
)
}
}
3. 跨组件通信
1. pusbub.js 跨组件通信插件
npm i pubsub-js --save-dev 安装
// 引入插件
import PubSub from 'pubsub-js'
// 父组件
export default class Com extends Component {
state = {
msg:"你好"
}
render(){
return(
<>
<ComA /> // 传值给A
<ComB />
</>
}
// 子组件 A
function ComA(props) {
return(
<>
- pubsub.js 跨组件通信 订阅 PubSub.subscribe
- msg 发布元素 data 接收的值
<button onClick={()=>{PubSub.subscribe('xxx', (_,data) =>{console.log(data);} )}}>订阅消息</button>
</>
)
}
// 子组件 B
function ComB(props) {
return (
<>
- pubsub.js 跨组件通信 发布 PubSub.publish
- xxx 是发布名称 data 是发布数据
<button onClick={()=>{PubSub.publish('xxx',data)}}>给兄弟传个东西</button>
</>
)
}
4. 组件优化(Class)
- Component 的 俩个问题
- 只要执行
setState()
,即使不改变状态数据,组件也会出现rander()
- 只当前组件重新
rander()
,就会自动出现rander
子组件,纵使子组件没有用到父组件的任何数据
- 原因
Component 中的
shouldComponentUpdate()
总数返回true
- 解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state 或 props 数据, 如果发生改变才返回true,如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state 或 props数据有变化才返回 true
注意:
只是进行state和props数据 浅比较,如果数据对象内部数据遍历,返回false
项目中一般使用PureCompoent 来优化
办法1: shouldComponentUpdate 闸门判断
// 渲染 rander 闸门判断
shouldComponentUpdate(nextProps, nextState) {
console.log(this.props, this.state); // 目前的props,state
console.log(nextProps, nextState); // 接下来要改变的目标props,state
// 父级组件 props 没用传值则无视 值判断 state
retrun !this.state.xxx === nextState.xxx
// 子组件 通常没用子级的state 判断 props
retrun !this.props.xxx === nextProps.xxx
}
方法2: 重写 Component ---> PureComponent
export default class Context extends Component {} ->
export default class Context extends PureComponent {}
不能对 state 进行操作
5. 插槽
原理: 利用组件传值原理 返回一个
rander函数
rander函数
内部返回一个Class组件
,在预留插槽处写{ this.props.rander(参数) }
// 父组件
export default class Com extends Component {
state = {
msg:"你好"
}
render(){
return(
<>
<ComA rander={(name)=><ComB name={name}></ComB> /> // 给子组件 A 的插槽插入 组件 B 形成父子
</>
}
// 子组件 A
function ComA(props) {
let state = {
name:"张三"
}
return(
<>
{this.props.rander(state.name)} // 预留插槽位 传参
</>
)
}
// 子组件 B
function ComB(props) {
return (
<>
我是子组件
{}
</>
)
}
6. setState
- setState(setateChang,[callback]) — 对象式的 setState
- setateChang 为状态改变对象
- callback 是可选的回调函数, 它的状态更新完毕、界面也更新后(rebder调用后) 才开始调用
- setState(updater,[callback]) — 函数式setState
- updater 是返回stateChange对象的函数
- updater 可以接收到state 和 props
总结:
1. 对象式的setState 是函数式的setState 的简写方式 2. 使用原则: 1. 如果新状态不依赖于原状态 ==> 使用对象式 2. 如果新状态依赖于原状态 ==> 使用函数式 3. 如果需要在setState() 执行后获取最新的状态数据要在第二个callback函数中读取
1. 对象式
this.setState({num:this.state.num+1},()=>{
console.log(num
// 回调函数
})
2.函数式
this.setState((state,props)=>{
retrun{num:state.num+1}
},
()=>{
// 回调函数
}
)
3. 数据规则
class
类 三大关键字extends
继承super
继承static
定义静态属性
- prop-types props的效验 增加代码健壮性
- 对于组件来说,props是外部传入的,无法保证组件数据类型一致性
- 实现步骤
- 1. 安装 cnpm i prop-types --save-dev
- 2. 引入 PropTypes 包
- 3. 使用 组件名.propTypes = {} 给组件效验规则
- 常见结构:array , bool , func , number , objcet , string,symbol ,element 元素,any 泛型(所有)
- React元素类型 node 文本节点
- props 效验默认值 defaultProps:xxx
- 必填项: isRequired
- 引入效验插件
import PropTypes from 'prop-types'
组件.propTypes= {
- 定义必填 末尾跟 .isRequired
msg:PropTypes.约束
- any.isRequired 仅限制必填
sgr:PropTypes.any.isRequired
- oneOfType([]) 多可选类型
obj:PropTypes.oneOfType([PropType.string,PropType.number])
- oneOf([]) 多可选值 (必选之一)
colors: PropTypes.oneOf(['red', 'blue'])
- 结构效验(对象) shape({})
people: PropTypes.shape({
name: PropTypes.string.isRequired,
id: PropTypes.number.isRequired
})
}
class ComB extends Component {
- static defaultProps:默认值
static defaultProps = {
msg:1 // 如果外面没有传递 msg 进来, msg 将默认为 1
}
}
4.Hooks (Func)
hooks
的本质,其实就一套能够使函数组件更强大,更灵活的“钩子”
1. useState 状态
函数组件 的useState 酷似 类组件的 state
// 引入
import {useState} from 'react'
export default function Hooks () {
- 使用 useState
const [num,setNum] = useState(0)
- num 是 存储state的变量 setNum 是修改 num 的函数方法
retrun (
<div>
<div>{num}</div> // 0
<button onClick={setNum(num+1)}></button> // 点击一次 num+1
</div>
)
}
总结:
1. useState() 初始值 只会在组件第一次时渲染
2. useState() 执行后返回的是一个 [] 第一个参数是 初始值,第二个参数是操作state 的函数
3. useState() 不能嵌套在 if/for 其他函数中
2. useEffect 生命周期
函数组件 的useEffect 酷似 类组件的 生命周期钩子
// 引入
import {useState,useEffect} from 'react'
export default function Hooks () {
- 使用 useState
const [num,setNum] = useState(0)
const [str,setStr] = useState("123")
- 使用 useEffect
{
useEffect(()=>{}) // 全部监听 (挂载,渲染)
useEffect(()=>{},[]) // 不监听参数 监听挂载
useEffect(()=>{return ()=>{}},[]) // 卸载组件
useEffect(()=>{},[str,num]) // 监听指定的state改变 默认执行一次
}
retrun (
<div>
<div>{num}</div> // 0
<button onClick={setNum(num+1)}></button> // 点击一次 num+1
</div>
)
}
总结:
1. useEffect(()=>{}) 分为执行函数 / 检测对象
2. 如果没有 检测对象 就检测所有对象
3. 根据参数不同 实现的生命周期不同
类似 Class 组件
componentDidMount 组件挂载
componentDidUpdate 组件更新
componentWillUnmount 组件销毁前
3. useRef Dom操作
函数 的useRef 类似与 Class 组件 ceateRef
import {useRef} from 'react'
export default function Hooks () {
rander(){
return(
<>
<div ref={myRef}>我是Ref</div>
<button onClick={getRef}>获取元素</button>
</>
)
}
function getRef(){
console.log(myRef); // 获取到元素
}
}
4. useContext
函数 的useContext 接收 creactContext 传递的 useState 遍历和函数
import {Component,useContext,createContext} from 'react'
- 创建 reacteContext 对象
const MyContext = createContext()
function Hooks() {
const [num, setNum] = useState(0);
render(
retrun(
<>
<div>我是父级</div>
- 将 useState 状态传给子孙级
<MyContext.Provider value={{num, setNum}}>
</MyContext.Provider>
</>
)
)
}
function A (){
retrun(
<>
<div>A</div>
</>
)
}
function B (){
- 接收 父级 传递的 useState 状态
const {num, setNum} = useContext(MyContext)
const add = () => {
setNum(num + 2);
};
retrun(
<>
<div>B</div>
<div>B组件</div>
<div>{num}</div>
<button onClick={add}>num+2</button>
</>
)
}
5. 组件优化(Func)
1. memo()
- 作用: 优化组件, 防止组件重新渲染
- 缺点: 如果参数中,含有函数不能阻止
- 优点: 缓存组件
- 场景: 不给子组件传递函数,并且子组件根据自身State来渲染
import React, { memo } from 'react';
- 子组件
1. export default memo(CompA)
2. export default memo(()=>{
retrun(
<div></div>
)
})
2. useMemo
- 作用: 优化组件, 防止组件重新渲染
- 缺点: 如果参数中,含有函数不能阻止
- 优点: 缓存State 状态
- 场景: 不给子组件传递函数,并且子组件根据自身State来渲染
import React, { memo,useMemo, useState } from 'react';
- 父组件
export default function ContentA() {
- 创建 State 状态
let [str,setStr] = useState("Howw");
let [num,setNum] = useState(0);
- 创建函数
let add = () => {
setNum(num+1)
setStr("Howw") - str 没用改变
}
- 调用 useMemo 对 State 进行缓存
let userInfo = useMemo(()=>{
return {num,str} // 缓存值
},[str]) - 监听依赖 str 没用发生改变 子组件rander 不执行
}
- 子元素
memo((props)=>{
return (
<div>
{props.userInfo.num}
{props.userInfo.str}
</div>
);
})
总结:
seMemo(()=>{return {} },[])
- 依赖为空 则不会 rander 传递数据的子组件 / 诺有 依赖 则 依赖改变 rander渲染
- 诺改变的 State 值 前后一致 则也不 触发 rander
3. useCallback
- 作用: 优化组件, 防止组件重新渲染
- 缺点: 不能缓存 State 值
- 优点: 可以缓存函数 (返回值是函数,每一次都是新函数)
- 场景: 调用函数改变State 数据
import React, { memo, useState,useCallback } from 'react';
- 父组件
export default function ContentA() {
- 创建 State 状态
let [str,setStr] = useState("Howw");
let [num,setNum] = useState(0);
- 创建 useCallback 函数
let useAdd = useCallback(() => {
setNum(num+1)
setStr("Howw") - str
// 如果需要设置 依赖 则
strStr(str=>"Howw")
},[]) - 如果不设置依赖 则函数只能触发一次 , 设置后 只对依赖属性进行更改
}
- 子元素
memo((props)=>{
return (
<div>
{props.userInfo.num}
{props.userInfo.str}
<button onClick={props.useAdd}></button>
</div>
);
})
总结:
useCallback(() => {
// 函数体
},[])
- 如果不设置依赖 则函数只能触发一次 , 设置后 只对依赖属性进行更改
- 如果函数执行 修改的数据与之前一样 则之后默认更新一次后 只执行一次
5.生命周期
1. 旧生命周期
18.x 以后 使用该生命周期 需要加上
UNSAFE_
前缀
componentWillUpdate 渲染前
componentWillReceiveProps 组件将要接收prpos(第一次不触发)
componentWillMount 挂载前
2. 新生命周期
getDerivedStateFrompProps 状态值完全取决于Props 结合 static 使用 需要返回值 了解即可
getSnapshotBeforeUpdate 更新之前 返回快照(更新前的状态值)
3. 常用生命周期
// 生命周期三个阶段
1.挂载阶段 执行顺序 constructot -> render -> componentDidMount
constructot 创建组件时, 先初始化(只执行一次)
作用: 1.初始化state 2.创建Ref 3.使用bind解决函数this指向问题
render 每次组件渲染都会触发
作用: 渲染UI视图(不能调用 this.setState) 虚拟DOM 转化为 真实DOM
componentDidMount 组件挂载(完成DOM渲染)后执行:初始化的时候(只执行一次)
作用:1.DOM操作
2.更新阶段 执行顺序 shouldComponentUpdate -> rander -> componentDidUpdate
shouldComponentUpdate 是否渲染页面 (retrun false 不渲染)
作用:阻止特定情况的页面渲染
rander 每次组件渲染都会触发
作用:渲染UI视图(注意:不能在 rander 中 调用 this.setState)
componentDidUpdate (更新前的props值,更新前的state值,快照值) 组件更新后 (DOM渲染后)
作用:1.DOM操作: 可以获取到更新后的DOM元素 2.发送网络请求
3.卸载阶段
componentWillUnmount 组件卸载
作用:清除定时器/清除订阅
6. 路由
1. Router 5
安装
pnpm i react-router-dom@5
1. 路由组件
路由组件和一般组件的区别 : 路由组件
props
接收三个固定的属性history
location
math
history:
go: ƒ go(n) -
goBack: ƒ goBack() -
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/"
search: ""
state: undefined
match:
params: {}
path: "/"
url: "/"
路由跳转的俩种模式
replace
替换当前页面push
默认模式
-
WithRouter 加工一般组件
-
BrowserRouter history 路由模式 因为state保存在 history 对象中 刷新数据不会丢失
-
HashRouter hash 路由模式(URL的哈希值) !因为 state 保存在 history 对象中 hash不调用 history 刷新会丢失数据
-
Link 路由导航 属性:
to = “/a”
模糊匹配 /a 层级匹配 顺序不能乱replace
重定向 (默认关闭)<Link to="/a"></Link>
-
NavLink 高亮导航 追加了一个属性
activeClassName
<NavLink to="/a" activeClassName = 'active'}></NavLink> 或者/ <NavLink to="/a" className={isActive => {return isActive ? 'active' : ''}></NavLink>
-
Switch 提高
Route
匹配效率 通常配合Route使用- Route 注册路由 属性:
path=“/a”
路由地址component = {模块名称}
跳转模块index=”/a“
默认路由 (不能和path同时出现在标签上)exact={true}
严格匹配 (有可能导致二级路由出问题)
<Switch> <Route index="/a" component={ComA} /> <Route path="/a" component={ComA} /> </Switch>
- Route 注册路由 属性:
-
Redirect 重定向 当所有路径不匹配的情况下 调用
<Switch> <Route index="/a" component={ComA} /> <Route path="/a" component={ComA} /> <Redirect to="/a"></Redirect> </Switch>
-
2. 多级路由
// 引用 样式
import ComA from './ComA';
import ComB from './ComB';
import { Redirect, Route, Switch, Link } from 'react-router-dom';
// 一级路由组件 B
function Aroute() {
return (
<div>
<div className="Cont">
<div>
{/* Link 导航标签 to 去那 */}
<Link to="/a">组件A</Link>
<Link to="/b">组件B</Link>
</div>
<div className="Cont-box">
{/* Switch 包裹 Route 防止 匹配多个同名路径 */}
{/* {/* Route 注册 路由 path路由地址 component 跳转模块* index 默认展示(index和path不能同时存在) /} */}
{/* <Route index="a" component={ComA} /> exact 开启精准匹配(不出问题不使用) */}
<Switch>
<Route path="/a" component={ComA} />
<Route path="/b" component={ComB} />
<Redirect to="/a"></Redirect>
</Switch>
</div>
</div>
</div>
);
}
export default Aroute;
// 二级路由组件 C
import ComC from './ComC';
import ComD from './ComD';
import { Redirect, Link, Route } from 'react-router-dom';
function ComB() {
return (
<div>
<h2>ComB</h2>
<div>
<Link to="/b/c">C组件</Link>
<Link to="/b/d">D组件</Link>
</div>
<div>
<Route path="/b/c" component={ComC}></Route>
<Route path="/b/d" component={ComD}></Route>
<Redirect to="/b/c"></Redirect>
</div>
</div>
);
}
export default ComB;
3. 路由传值
1. params 传值
// 父组件 - 字符串拼接 /参数
<Link to={`{/a/${1}/${组件1}`}>1组件</Link>
<Link to={`{/a/${2}/${组件2}`}>2组件</Link>
- 声明接收 params /:id 一一对应
<Route path="/a/:id/:text" component={ComE}></Route>
// 子组件 获取 params 对象
const {id, text} = props.match.params
2. search 传值
- 父组件
<Link to={`/b/d/e/?id=${item.id}&text=${item.text}`}>{item.name}</Link>;
<Route path="/b/d/e" component={Com}></Route>
- 子组件
console.log(props.location.search); // urlencoded "?id=xxx&text=xxx"
// import qs from 'querystring' 转换方式已废弃
4. state 传值
- 父组件 - 传递一个对象
<Link to={{ pathname: '/b/d/e', state: { id: item.id, text: item.text } }} >
<Route path="/b/d/e" component={Com}></Route>
- 子组件
const { id, text } = props.location.state;
4. 编程式路由
1. 路由组件
- 通过代码 实现路由跳转
- 1. push 模式
props.history.push()
- 2. replace
props.history.replace()
- 传值方式
// 1. params 传值
props.history.push(`/b/d/e/${id}/${text}`);
props.history.replace(`/b/d/e/${id}/${text}`);
// 2. search 传值
props.history.push(`/b/d/e/?id=${item.id}&text=${item.text}`);
props.history.replace(`/b/d/e/?id=${item.id}&text=${item.text}`);
// 3. state 传值
props.history.push({ pathname: '/b/d/e', state: { id, text } });
props.history.replace({ pathname: '/b/d/e', state: { id, text } });
- 前进 后退 go(1/-1) goBack() goForward()
- 前进
props.history.goForward()
props.history.go(n) 前进n步
- 后退
props.history.goBack()
props.history.go(-n) 后退n步
2. 一般组件
withRouter 加工一般函数 返回路以组件 (用于 非路由组件引用路由跳转的场景)
import {withRouter} from "react-router-dom"
export default withRouter(xxx组件)
2. Router 6
安装
pnpm i react-router-dom@6
1. Router5-6 的区别
-
组件
<Routes/>
替换<Switch/>
新增
<Outlet/>
<Navigate>
-
属性
<Route/>
属性component={About}
被替换为element={<About/>}
-
函数 新增
useParams
、useNavigate
、useMatch
-
<Navigate>
跳转 只要渲染就会切换路径 属性replace = {false}
跳转模式 默认 false<Route path="/" element={<Navigate to="/a" />}></Route>
-
<Route/>
属性 caseSensitive 区分大小写 -
<NavLink/>
高亮 className 写法end
子级选中了 父级失去高亮<NavLink to="/home" className={({isActive})=>{ isActive?'active','' }}> <NavLink/>.
-
<Outlet/>
指定路由呈现的位置
-
2. Hooks 路由传值
1. useRoutes
- 路由表
import React, { lazy } from 'react';
import { Navigate } from 'react-router-dom';
const ComA = lazy(() => import('../component/Aroute/ComA'));
const ComB = lazy(() => import('../component/Aroute/ComB'));
const ComC = lazy(() => import('../component/Aroute/ComB/ComC'));
const ComD = lazy(() => import('../component/Aroute/ComB/ComD'));
const routeList = [
{
path: '/a',
element: <ComA />,
},
{
path: '/b',
element: <ComB />,
children:[ -- 二级路由
{
path: 'c',
element: <ComC />,
},
{
path: 'd',
element: <ComD />,
},
]
},
{
path: '/',
element: <Navigate to="/a" />,
},
];
export default routeList;
-- 父级 组件
import { useRoutes, Link } from 'react-router-dom';
import router from '../../routes';
-- useRoutes
const elementRoutes = useRoutes(router);
{elementRoutes}
- B组件
import { Outlet } from 'react-router-dom';
- Outlet 指定路由呈现的位置
<Outlet />
- C组件
<Link to="d">D组件</Link> <Link to="c">C组件</Link>
2. useParams
- 获取路由 Params 值
- 路由
path: 'e/:id/:name/:text'
- 获取
import { useParams } from 'react-router-dom';
const { id, name, text } = useParams();
3. useMatch
- 获取路由 Params 值 (不常用)
- 路由
path: 'e/:id/:name/:text'
- 获取
import { useMatch } from 'react-router-dom';
const { params } = useMatch('/b/d/e/:id/:name/:text');
const { id, name, text } = params;
4. useSearchParams
- 获取路由 search 值
- 路由
path: 'e'
- 获取
import { useSearchParams } from 'react-router-dom';
// 获取 search 获取 search 函数 、 setSearch 修改 search 数据
const [search,setSearch] = useSearchParams();
// 获取 search
const id = search.get('id');
const name = search.get('name');
const text = search.get('text');
// 修改 search
setSearch('id=2&name=jj&text=jb');
5. useLocation
- 获取路由 search 值
- 路由
path: 'e'
import { useLocation } from 'react-router-dom';
// 获取 search 获取 search 函数 、 setSearch 修改 search 数据
const data = useLocation();
// 获取 search
const {search} = data
- 获取路由 state 值
- 路由
path: 'e'
import { useLocation } from 'react-router-dom';
// 获取 search 获取 search 函数 、 setSearch 修改 search 数据
const data = useLocation();
// 获取 search
const {state} = data
3. Hooks 编程式导航
useNavigate
// 引入 useNavigate
import { useNavigate } from 'react-router-dom';
// 获取 navigate
const navigate = useNavigate()
// 跳转
navigate('url', // 路由路径
{ -- 可写
replace:false, // 跳转模式
state:{} // 携带 state 参数
})
// 前进
navigate(1)
// 后退
navigate(-1)
4. Hooks 其他
1. useInRouterContext
- 判断:是否在路由上下文中
console.log(useInRouterContext());
2. useNavigationType
- 判断: 是否进入路由的模式
console.log(useNavigationType());
3. useOutlet
- 显示: 当前点击的路由组件 的嵌套组件(点中项)
console.log(useOutlet());
4. useResolvedPath
- 给他一个 URL 以及参数 返回 一个URL解析对象
console.log(useResolvedPath('e/?a=1&b=2ç'));
{pathname: '/b/d/e/', search: '?a=1&b=2&', hash: '#231'}
hash: "#231"
pathname: "/b/d/e/"
search: "?a=1&b=2&"
}
3. 懒加载
-
lazy
-
Suspense
// 引入 import React, { lazy, Suspense } from 'react'; // 引入 组件 lazy const ComA = lazy(() => import('./ComA')); // Suspense 包裹路由组件 fallback 加载中提示组件 <Suspense fallback={<h1>Loding....</h1>}> <Switch> <Route path="/a" component={ComA} /> <Route path="/b" component={ComB} /> {/* 多个 Route path 相同 会一同战术 !- 不推荐 */} <Route path="/b" component={PusSubA} /> <Redirect to="/a"></Redirect> </Switch> </Suspense>
7. 错误边界
保证代码正常执行
- 在报错 子组件的引用父组件定义 getDerivedStateFromError 钩子 结合 static 捕获后代生命周期的错误 ----- 只用于生产环境(上线)
// 定义父组件
export default class Fon extends Component {
state = {
hasError:"" - 1. 存储 子组件的状态
}
- 2. 当 Fon 的子组件报错的时候,会触发 getDerivedStateFromError 调用,并携带错误信息
static getDerivedStateFromError(error){
console.log(error)
retrun {hasError:error} - 需要返回一个对象
}
- 3.渲染组件时出错 (钩子)
componentDidCatch(error,info){
- 统计错误 反馈给服务器
console.log(error,info)
}
render(){
return(
<div>
<h2>我是父组件</h2>
- 4.处理子组件的错误
{this.state.hasError ? <h2>当前网络错误</h2> : <Son />}
<div/>
)
}
}
// 函数子组件
function Son(){
return(
<>
<h2>我是子组件</h2>
</>
)
}
8. redux
安装 redux
pnpm add redux
安装 redux-thunk - 处理 异步 action
pnpm add redux-thunk
1. 文件布局
- 文件布局
- App.jsx // app 主文件
- pages // 路由文件
- components // 组件UI
- containers // 容器层
- redux
- store.jsx // 创建 store 对象
- reducer // 封装 reducer 工具函数
- index.jsx // 合并 reducer 根据函数 然后导出
- xxx.jsx
- action // 生成 action 对象
-xxx.jsx
- constant.jsx // 定义 action 对象 type类型 的常量值 (可写) (防止写错)
2. API
- store.getState() 获取 store 的状态值 第一次调用 携带undf 记得设置默认值
- store.dispatch() 调用封装在reducer 的工具函数 参数: action 执行对象 { type, data: value * 1 }
- store.subscribe() 调用监听 store 函数 携带回调函数
3. 代码流程
1. store.jsx
- createStore : 创建 redex (可能已废弃)
(1). 引入 createStore 创建 redex
// 引入 处理异步 action 的 中间件 applyMiddleware
import { createStore, applyMiddleware } from 'redux';
(2). 引入 reducer 组件
import countReducer from './reducer';
import pentReducer from './reducer/Pent';
// 引入 redux-thunk 用于支持异步 action
import thunk from 'redux-thunk';
// 引入 redux-devtools-extension 用于支持 rudex 开发者工具
import { composeWithDevTools } from 'redux-devtools-extension';
(3). 合并 reducer
const allReducer = combineReducers({ list:pentReducer, count:countReducer });
(4). 暴露 store
1. 有异步 无开发工具
export default createStore(allReducer, applyMiddleware(thunk));
1. 无异步 有开发工具
export default createStore(allReducer, composeWithDevTools());
1. 有异步 有开发工具
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)));
2. reducer.jsx
// 调用定义好的常量 (可写)
import { ADDCREMENT, DELCREMENT } from './constant';
(1). // 暴露 定义的 reducer 函数
expoer default function countReducer(preState = 0,action) {
// preState 之前的状态 第一次执行默认 undf 所以要设置默认值 action 执行对象
(2). 从 action 中获取:type , data
const {type,data} = action;
(3). 根据type 决定执行的步骤方法
switch (type) {
case ADDCREMENT: // 如果是加
return preState + data;
case DELCREMENT: // 如果是减
return preState - data;
default:
return preState; // 初始化
}
}
3. action.jsx
(1). 定义 reducer 工具函数 需要的 action 对象
// 调用定义好的常量 (可写)
import { ADDCREMENT, DELCREMENT } from './constant';
const addcontAction = data => ({ type: ADDCREMENT, data });
const delcontAction = data => ({ type: DELCREMENT, data });
// 异步 action 返回的是 函数
const addAysnAction = (data, time) => {
return dispatch => {
setTimeout(() => {
dispatch(addcontAction(data));
}, time);
};
};
(2). 暴露出去
export { addcontAction, delcontAction,addAysnAction };
4. constant.jsx
(1). 定义 action 对象 type类型 的常量
const ADDCREMENT = '+';
const DELCREMENT = '-';
(2). 暴露出去
export { ADDCREMENT, DELCREMENT };
5. container 容器
- (1). 引入 Count 的 UI组件
import CountUI from '../../components/Comt/Comt';
- (2). 引入 action
import { addcontAction, delcontAction, addAysnAction } from '../../redux/action';
- (3). 引入 connect 用于 连接UI组件 与 redux
import { connect } from 'react-redux';
-(5). mapStateToProps 的返回值 传递给 ContUI组件 props 的 state 对象
function mapStateToProps(state) {
return { count: state };
}
-(5). mapDispathToProps 的返回值 传递给 ContUI组件 props 的 操作 state 对象
function mapDispathToProps(dispath) {
return {
add: data => {
dispath(addcontAction(data));
},
del: data => {
dispath(delcontAction(data));
},
// 异步加
asyncAdd: (data, time) => {
dispath(addAysnAction(data, time));
},
};
}
(4). 使用 connect()() 创建并暴露一个 Cont 容器组件
export default connect(mapStateToProps, mapDispathToProps)(CountUI);
// 简写
export default connect(state => ({ count: state.count }), {
add: addcontAction,
del: delcontAction,
asyncAdd: addAysnAction,
})(CountUI);
6. App.js
import React from 'react';
- (1). 引入 Cont 容器组件
import Cont from './containers/Cont';
- (2). 引入 store
import store from './redux/store';
function App() {
return (
<div>
- (3). 添加 store
<Cont store={store} />
</div>
);
}
export default App;
// 或
import React from 'react';
// 引入 Cont 容器组件
import Cont from './containers/Cont';
// 引入 store
import store from './redux/store';
//
+ import { Provider } from 'react-redux';
function App() {
return (
<>
+ <Provider store={store}>
<Cont />
+ </Provider>
</>
);
}
export default App;
7. component 组件引入
1. rudex - component
(1). 引入 store 用于获取全局状态
import store from './redux/store';
(2) 引入 action 用于创建 action对象
import { addcontAction, delcontAction,addAysnAction } from './redux/action';
// 加法
addNum = type => {
const { value } = this.select;
// (4). 调用 reducer 工具
store.dispatch(addcontAction(value * 1));
};
// 减法
delNum = () => {
const { value } = this.select;
// (4). 调用 reducer 工具
store.dispatch(delcontAction(value * 1));
};
// 异步
yibu = () => {
const { value } = this.select;
// 调用reducer 工具 异步任务
store.dispatch(addAysnAction(value * 1, 1000)); -- 可以不适应 异步rudex
// 正常异步任务
setTimeout(()=>{
store.dispatch(addcontAction(value * 1));
},1000)
};
render() {
return (
<div>
{/* (3). store.getState() // 接收reducer 返回的数据 第一次调用 返回默认值 */}
<h1>当前求和{store.getState()}</h1>
<div>
<input type="text" ref={c => (this.select = c)} />
</div>
<div>
<button onClick={this.addNum}>+</button>
<button onClick={this.delNum}>-</button>
<button onClick={this.yibu}>异步</button>
</div>
</div>
);
(5). 检测 redex 中 状态的改变 调用 this.setState({}) 重新渲染 render
componentDidMount() {
// 检测 redux 中状态的变化
store.subscribe(() => {
this.setState({});
});
}
// 或 在 mian.jsx 文件内
// 引用 store 用于获取全局状态
import store from './redux/store';
// 监听 store 的数据改变 改变则重新渲染
store.subscribe(() => {
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
});
2. react-rudex - component
import React, { Component } from 'react';
export default class Anli extends Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {}
addNum = () => {
const { value } = this.select;
// 调用 this.props 传入的方法
this.props.add(value * 1);
};
delNum = () => {
const { value } = this.select;
// 调用 this.props 传入的方法
this.props.del(value * 1);
};
tongbu = () => {
const { value } = this.select;
// 调用 this.props 传入的方法
if (this.props.count % 2 === 1) {
// 调用 this.props 传入的方法
this.props.add(value * 1);
}
};
yibu = () => {
const { value } = this.select;
// 调用 this.props 传入的方法
this.props.asyncAdd(value * 1, 1000);
};
render() {
return (
<div>
<h1>当前求和{this.props.count}</h1>
<div>
<input type="text" ref={c => (this.select = c)} />
</div>
<div>
<button onClick={this.addNum}>+</button>
<button onClick={this.delNum}>-</button>
<button onClick={this.tongbu}>奇数加</button>
<button onClick={this.yibu}>异步加</button>
</div>
</div>
);
}
}
3. 整合 constant 、component
import React, { Component } from 'react';
// 引入 action 按需引入
import { addcontAction } from '../../redux/action';
// 引入 connect 用于 连接UI组件 与 redux
import { connect } from 'react-redux';
// UI组件
class ALLComt extends Component {
addNum = () => {
const { value } = this.select;
this.props.add(value * 1);
};
render() {
return (
<div>
{/* store.getState() // 接收reducer 返回的数据 第一次调用 返回默认值 */}
<h1>当前求和{this.props.count}</h1>
<div>
<input type="text" ref={c => (this.select = c)} />
</div>
<div>
<button onClick={this.addNum}>+</button>
</div>
</div>
);
}
}
// 简写 导出
export default connect(state => ({ count: state.count }), // 映射状态
{add: addcontAction}) // 映射方法
(ALLComt);
9. 路径请求
public > index 引入文件路径
- 文件路径
/
开头 - 文件路径
%PUBLIC_URL%/
- 路由模式 `hash
10. 其他
1. 高阶函数
参数或者返回值 是 函数的函数 就是高阶函数
- 高阶函数:如果一个函数符合下面2个规范中的其中一个,那该函数就是高阶函数
-1.诺A函数,接收的参数是一个函数,那么A就可以称之为高阶函数.
-2.诺A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数.常见的高阶函数有:Promise、setTimeout、arr.map()等
- 函数柯理化:通过函数调用继续返回函数的方式,实现多次接收参数最后一次统一处理的函数编码
function sum(a){
return(b)=>{
return(c)=>{
return a+b+c
}
}
}
2. 纯函数
- 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- b不能调用Date.new() 或者 Math.random() 等不纯方法
- redux 的 reducer 函数必须是一个纯函数