React 项目基础
创建项目
使用官方脚手架
通过npm create-react-app 能创建一个基本的react项目
只做了react基本的搭建和构建,没有配上任何路由和状态管理。项目使用webpack构建
使用一些市场上的集成脚手架
官方脚手架提供的项目模板非常简单,因此也有很多集成的脚手架
典型的比如umi。这一类脚手架创建出来的项目会集成好多功能,比如路由、mock
核心库
react react核心库,提供react的个个功能
React-dom 提供一些dom操作方法用于把react创建出来的react对象挂载到真正的htmlDom中,或者从htmlDom中卸载。核心作用类似于vue的mount
项目入口文件
App根组件 React 组件就是一个函数形式,React函数组件是从React 16.8版本开始引入的
!!! jsx 只能返回一个根元素
jsx 插值表达式 jsx是javascript跟html结合的语法
使用 {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function App(){ // 可以将img标签非必须得属性提取出来写 const imgData = { className: 'small', style: { width: 200, height: 200, backgroundColor: 'red' } } return ( <div> <!-- 这里不是展开运算符, 是jsx的插值表达式 --> <img src="./logo.svg" alt="" {...imageData}></img> </div> ) /* 展开运算符是需要一个对象自变量进行接受的,直接{...imageData}在js语法中是会报错 */ }
Fragment 当渲染的标签需要在标签上添加属性便不能在使用幽灵标签 - 而使用Fragment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { Fragment } from "react" function App(){ const list = { {id:1, name: '小张'}, {id:2, name: '小光'} } const listContent = list.map(item =>{ <Fragment key={item.id}> <li>{item.name}</li> <span>yyds</span> </Fragment> }) return ( <ul>{listContent}</ul> ) } export default App;
React组件和jsx
React 组件分类
1 2 3 function Hello() { return <div>hello</div> }
1 2 3 4 5 class Hello extends React.Component { render() { return <div>hello</div> } }
通过React.createElement() 创建组件,与vue类似( 首字母大写)
1 2 3 function Hello(){ return React.createElement('div',[],"hello,word") }
jsx 特点
jsx里面渲染不同内容的区别
字符串;数组: 直接渲染
方法:无法渲染
对象:只能渲染element
布尔值:不渲染任何内容
数组:把数组里面的每一项单独渲染
undefine、null:不渲染任何内容
表达式:运行表达式
react事件绑定
规则模式
类似于原生,on+方法名( 首字母大写)
一定要赋值给事件一个方法
注意:
不做处理的情况下,this会指向undefind
给到事件绑定的一定得是一个方法,不要直接调用方法,调用方法只会在页面初次与渲染指向方法( 与vue不同)
事件绑定的其他操作
1 <div onClick={ this.f1.bind(this, 1, 2)}></div>
当没有传递任何参数时,事件对象就是第一个参数;传递参数时,事件对象就是最后一个参数
该事件对象是合成的事件对象,并非原生事件对象,原生事件对象在e.nativeEvent
1 2 e.stopPropagation() e.preventDefault()
3.使用了类组件可以在类初始化函数内进行 this.f1.bind() 的操作,这样就可以直接使用 this.f1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // ./Event.tsx import { Button, Space} from 'antd' // 使用antd UI组件库 import React, {Component} from 'react' export default class Event extends Component { // 类初始化 - constructor constructor(porps:any){ super(porps) // 此处先修改了this指向,后续onClick内直接调用函数即可 this.click1 = this.click1.bind(this) } click1() { console.log('click1') } click2 = ()=>{ console.log('click2') } click3(str:string) { console.log(str) } click4 = (str:string)=>{ console.log(str) } render(){ return ( <div> <Space> <Button type='primary' onClick={this.click1}>click1</Button> <Button type='primary' onClick={this.click2}>click2</Button> <Button type='primary' onClick={this.click3.bind(this, 'click3')}>click2</Button> <Button type='primary' onClick={this.click4.bind(this, 'click4')}>click2</Button> <Button type='primary' onClick={()=>{this.click4('click4')}}>click2</Button> </Space> </div> ) } }
react 组件中的响应式数据 在 React 中,如果不定义响应式数据,视图就不会自动更新。React 是通过比较虚拟 DOM 树的差异来决定何时重新渲染组件的。而为了触发视图更新,你需要显式地调用 setState
方法来更改组件的状态。
1. react 响应式原理
* react不能像vue一样直接修改触发更新
* react修改能改的值,但无法触发更新,因为react没有像vue一样监听get和set,而是在调用setState的时候调用react的更新操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import './App.css' import React from 'react' class App extends React.Component { // 响应式数据 state = { a:0 } addA = ()=>{ // 调用setState 执行页面更新操作 this.setState({ a: ++this.state.a }) // 或者在外头操作,然后只是单纯的调用setState方法,触发更新 this.state.a += 1; this.setState({}) } render(){ return <div className='App'> {this.state.a} <button onClick={this.addA}> +1 </button> </div> } }
setState工作流程
关键点:
通过浅合并来修改数据
调用setState方法会触发更新,修改state并不会触发更新
在修改对象属性的时候需要将数据写全,不然会覆盖;可以先将原对象展开,在赋值
setState修改变量操作是异步的,需要获取到修改后的数据需要在第二个参数下获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import './App.css' import React from 'react' class App extends React.Component { // 响应式数据 state = { a:0 } addA = ()=>{ // 调用setState 执行页面更新操作 this.setState({ a: ++this.state.a },()=>{ // 在这里获取最新数据 console.log(this.state.a) }) // 或者在外头操作,然后只是单纯的调用setState方法,触发更新 this.state.a += 1; this.setState({}) } render(){ return <div className='App'> {this.state.a} <button onClick={this.addA}> +1 </button> </div> } }
setState方法多次修改,会合并为一次,统一更新
setState返回触发更新,不管你是否修改,这就造成了一个新问题,重复修改相同的值也会让组件进行更新
1 2 3 4 5 6 7 8 9 10 11 12 // 解决方法 可以使用 React.PureComponent // 缺点 当使用React.PureComponent的时候 如果修改的是数组和对象,就对导致页面没有更新 因为数组和对象的判断是否改变是判断内存地址是否改变 // 解决方法 解除引用,重新赋值新数组或对象 this.setState({ arr:[ ...this.state.arr], object: { ...this.state.object} })
一定不要在render里直接使用setState ( 在React.Component 下会触发死循环)
如果不使用响应式数据(例如使用 useState [setState的hock] 或 useReducer),但是需要手动触发更新视图,可以使用 forceUpdate 方法
forceUpdate 是 React 组件的内置方法。它用于强制组件重新渲染并更新视图。然而,官方文档建议尽量避免使用 forceUpdate,因为它会跳过 React 的优化流程,并可能导致性能问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 /* 函数式组件 */ import React, { useRef } from 'react'; function MyComponent() { //使用了 useRef 来创建一个非响应式的引用变量 countRef const countRef = useRef(0); function handleClick() { countRef.current += 1; // 更新计数值 component.forceUpdate(); // 强制组件重新渲染 //在实际开发中,除非特殊情况下需要手动操作更新视图,一般不建议频繁地使用 forceUpdate 方法。 } return ( <div> <p>Count: {countRef.current}</p> <button onClick={handleClick}>Increment</button> </div> ); } /* 类组件 */ class MyComponent extends React.Component { handleClick() { // 在事件处理程序中调用 forceUpdate() this.forceUpdate(); } componentDidMount() { // 在生命周期方法中调用 forceUpdate() this.forceUpdate(); } render() { // 组件的渲染逻辑 return ( <div> <button onClick={this.handleClick.bind(this)}>强制更新</button> {/* ... */} </div> ); } }
React 条件渲染和列表循环 条件循环
react 没有像vue一样的指令,一切操作本质上都是我们通过运算生成不同的内容,拿去渲染,得到不同页面
条件渲染本质
原则:
react 渲染undefined,null,空字符串,false不会渲染成任何内容
如果渲染一个jsx编写的html元素,就会渲染成页面上的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import './App.css' import React from 'react' class App extends React.PureComponect { state = { show: true } function Hello() { return <div>hello</div> } f1(){ if(this.state.show) { return <div>hello</div> } else { return "" } } render(){ return <div className="App"> <div>条件渲染</div> // 三元运算符 <div>{this.state.show ? <Hello/> : ""}</div> // 与运算符 当this.state.show 为false时,不会向后执行 <div>{this.state.show && <Hello/>}</div> // 使用函数判断 <div>{this.f1()}</div> <button onClink={()=>{this.setState({show: !this.state.show})})}>{this.state.show ? "隐藏" : "显示"}</button> </div> } }
列表循环 原则:
渲染一个数组会把数组里的灭意向单独取出渲染
那么我们编写一个里面存放的都是html结构的数组,就会渲染成列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import './App.css' import React from 'react' class App extends React.PureComponect { state = { array_1: [1,2,3] // [1,2,3] => [<div>1</div>, <div>2</div>, <div>3</div>] } getArr(){ return this.state.array_1.map((item) =>{ <div key={item}>{item}</div> }) } render(){ {this.getArr()} } }
react的思想
更少的封装,更高的自由度,能够让使用者更加高度的去自定义实现,不想vue一样必须遵守许多规则
纯粹的js,没有什么特殊的指令
表单绑定
基本思路
react中很多思路都是按照原生的操作去做的,表单绑定也是如此
原生表单获取表单输入值,我们可以通过监听input,change等事件,然后获取e.target.value
如果要设置表单的值,通常设置value属性,如果选择框是checked属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // input 双向数据绑定 import './App.css' import React from 'react' class App extends React.PureComponect { state = { inputValue : "" } changeInput(e){ } render(){ return <div className="App"> inputValue: {this.state.inputValue} <input type="text" value={this.state.inputValue} onInput={(e)={ this.setState({ inputValue: e.target.value }) }} /> </div> }
受控组件( 双向监听)
表单值可以由使用者手动去设置,也就是进行了双向绑定。我们可以修改state 数据从而影响到表单
非受控组件( 单向监听)
表单的值由自己维护。我们只能获取表单的值,不能直接修改state 数据来修改表单值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // checkbox 复选框 双向数据 import './App.css' import React from 'react' class App extends React.PureComponect { state = { checkboxArr : [] } handleChecked = ()=>{ let arr = [...this.state.checkedArr] if(e.target.checked){ arr.push(e.target.value) }else{ arr.splice(arr.indexOf(e.target.value),1) } this.setState({ checkedArr: arr }) } render() { return <div className="App"> {this.state.checkboxArr} <input onChange={this.handleChecked} checked={this.state.checkedArr.indexOf("c1") !== -1} value="c1" type="checkbox" name="choose" />选项一 <input onChange={this.handleChecked} checked={this.state.checkedArr.indexOf("c2") !== -1} value="c21" type="checkbox" name="choose" />选项二 <input onChange={this.handleChecked} checked={this.state.checkedArr.indexOf("c3") !== -1} value="c3" type="checkbox" name="choose" />选项三 </div> } }
Prop和组件通信、插槽
props是react的核心
在react中,一切写在组件上的属性和子节点都被规划为了props。所以props是react很多功能的根本。父子传值,插槽全是基于props,不像vue又事件监听,emit,专门的插槽这一类东西
1 2 3 4 5 6 7 8 9 10 11 12 // Son.js import React from "react" class Son extends React.PureComponent { render() { // 通过this.porps 获取父组件传给子组件的值 return <div> {this.porps.message} </div> } } export default Son;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // App.js import './App.css' import React from 'react' import Son from './Son.js' class App extends React.PureComponect { state = { msg: "父组件给子组件传值" } render() { return <div className="App"> <Son message={this.state.msg}></Son> </div> }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 子组件可以接受到props, 或者{title, content} 解构 function Artitle(props){ return ( <div> <h2>{porps.title}</h2> <p>{props.content}</p> </div> ) } export default function App(){ return ( <> <Artitle title="标题" content="内容"/> </> ) }
props 类型验证和默认值 - defineProps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // Son.js import React from "react" class Son extends React.PureComponent { render() { // 通过this.porps 获取父组件传给子组件的值 return <div> {this.porps.message} </div> } } // 传值类型判断 第三库proptypes Son.propTypes = { // 必须自定义函数判断类型,不能像vue一样写类型 message: funciton(props){ if (typeof props.message !== "string"){ throw new Error("message must be a string") } }, } // 默认值设置 Son.defaultProps = { massage: "message is undefind", } export default Son;
模拟Vue中的插槽
插槽本质上就是给子组件的html内容由父组件传入,jsx的加持下,我们可以把html像普通的字符串,数组一样传递,所以插槽只需要直接作为props传入就可以,内容放在了children下( 函数式方法一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // Son.js import React from "react" class Son extends React.PureComponent { state ={ scoptValue : "SonValue" } render() { // 父组件会将插槽内容通过porps的children 数组中存放,直接渲染即可 return <div> <!-- 普通插槽 默认名字为children --> {this.porps.children} <!-- 具名插槽 --> {this.props.SonName01} <!-- 作用域插槽 利用父组件传的组件调用子组件的值 --> {this.props.scopeSlot(this.state.scoptValue)} </div> } } export default Son;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // App.js import './App.css' import React from 'react' import Son from './Son.js' class App extends React.PureComponect { render() { return <div className="App"> <!-- 普通插槽 默认名字为children --> <Son> <div>给子组件的插槽内容</div> </Son> <!-- 具名插槽 --> <Son SonName01={<div>给子组件的插槽内容</div>}></Son> <!-- 作用域插槽 --> <!-- 父组件传递的插槽只能使用父组件的数据,如果需要用到子组件内的数据就可以用作用域插槽 --> <Son scopeSlot={(scope)=>{ return <div>{scope}</div>}}></Son> </div> }
父组件给子组件传值
props不能直接修改父组件传递过来的值( vue同意),我们需要通过子组件触发父组件事件,从而改变父组件的值,达到一个传递值的效果
.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // App.js import './App.css' import React from 'react' import Son from './Son.js' class App extends React.PureComponect { state { messAge: "hello" } changeMessage(msg) => { this.state.messAge = msg } render() { return <div className="App"> <!-- 将方法通过porps传递给子组件 --> <Son message={this.state.messAge} changeMes={this.changeMessage}></Son> </div> } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // Son.js import React from "react" class Son extends React.PureComponect { render() { return <div> <!-- 点击时触发父组件方法,通过父组件的方法修改父组件的值 --> {this.props.changeMes} <button onClick={()=>{ this.props.changeMes("hello word") }}> 修改父组件值 </button> </div> } } export default Son;
兄弟组件传值
子组件1 -> 父组件 -> 子组件2
eventbus
多级的,在函数式写法中有一个hocks - Context进行操作
React中的样式操作
class 类名设置
必须为className
类名和样式写在css文件里
必须接受一个字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 // App.js import './App.css' import React from 'react' class App extends React.PureComponent { render() { return ( <div className="header"> header </div> ) } } export default App;
1 2 3 4 5 // App.css .header { background-color : red; }
style 内联
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // App.js import './App.css' import React from 'react' class App extends React.PureComponent { render() { return ( <!-- 第一个{} 是模板表达式, 第二个{} 是对象 --> <div style={ { color: red, fontSize: 12px. } }> header </div> ) } } export default App;
如何解决组件专用css
将导入的css文件使用.model.css 模块化方式导入,对象的方式使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // App.js import styleModel from './App.model.css' import React from 'react' class App extends React.PureComponent { render() { return ( <!-- 使用 " " 的方法对css类名进行空格分开 --> <div className={ styleModel.header + " " + styleModel.appStyle}> header </div> ) } } export default App;
1 2 3 4 5 6 7 8 // App.model .css .header { background-color : red; } .appStyle { color : pink; }
当需要动态的删减类名,如何更舒服的控制类名的添加和减少
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import React from 'react' class Son extends React.PureComponent { state { hasSonClass: true, } render() { return ( <div className={"App " + (this.state.hasSonClass ? "Son" : "")}></div> <button onClick(()={ this.state.hasClass = !this.state.hasClass })>动态添加Son style class名</button> ) } } // 第二种方法 // 使用第三方库 classnames // 如果使用了模块化方法后 需要将classnames this指向为模块化,引入的classnames更改为classnames/bind /** import classnames from "classnames/bind" import sonStyle from "./son.model.css" let bindCN = classnames.bind(sonStyle) */ import classNames from "classnames" class Son extends React.PureComponent { state = { hasSonClass: true, } render() { return ( <div className={classNames({ App: true, Son: this.state.hasClass })}></div> <button onClick(()={ this.state.hasClass = !this.state.hasClass })>动态添加Son style class名</button> ) } }
React 生命周期
react 生命周期分为三大阶段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import classNames from "classnames" class App extends React.PureComponent { // 首先执行 类似类初始化方法 constructor(props) { super(props) this.state = { } console.log("1") } // 第二执行 getDerivedStateFromProps 静态方法 static getDervedStateFromProps(props,state){ console.log("2") return null } // 第三执行 render函数 render() { console.log("3") } // 最后执行componentDidMount componentDidMount(){ console.log("4") } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import classNames from "classnames" class App extends React.Component { state = { msg: "hello" } // 首先执行 getDerivedStateFromProps 静态方法 static getDervedStateFromProps(props,state){ console.log("1") return null } // 第二执行 shouldComponentUpedate PureComponent下不执行 shouldComponentUpedate(props, state) { // 优化生命周期 console.log("2") //如果return 为false 则以下不执行 // this.state 是修改前的数据, state是修改后的数据 let change = true for (let item in this.state){ if(state[item] !== this.state[item]){ change = false } } return change } // 第三执行 render函数 render() { console.log("3") return <div> <div>{this.state.msg}</div> <button onClick={()={ this.state.msg = "hello word" }}>修改msg</button> </div> } //第四执行 getSnapshotBeforeUpdate getSnapshotBeforeUpdate(){ console.log("4") } // 最后执行componentDidUpdate componentDidUpdate(){ console.log("5") } }
卸载 ( componentWillUnmount)
重点生命周期讲解
render 通过render函数来执行决定组件渲染什么内容,所以无论是更新还是初次挂载都必须执行render
compinentDidMount 组件挂载完成,一般用来做些 页面初始化操作,比如初始化请求,echart绘制等,也就是vue的mounted里能做的事
shouldComponentUpdate 更新阶段调用,如果return false则不会执行render函数继续更新,从而达到阻止更新的效果,一般用来做性能优化
componentDidUpdate 更新完成,等同于vue的update
componentWillUnmount 组件即将卸载,通常做些全局事件的监听的卸载;定时器,计时器的卸载等。来优化性能
vue的更新机制与react的问题所在
ref和context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import React from 'react' class App extends React.PureComponent { let div1 = React.createRef(); componentDidMount() { //废弃 console.log(this.refs.dev1) console.log(this.div1.current) } render() { return ( <!-- 幽灵节点 --> <> <div className="App" ref={this.div1}> div </div> </> ) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // App.js import React from 'react' import Son from './Son' // Context 是一个组件,并暴露出去; 函数式中直接使用 createContext() hocks export let Context = React.createContext() class App extends React.PureComponent { state = { msg: "父组件消息,传给后代组件" hello: "word" } render() { return ( <> <dev className="App"> App </dev> <Context.Provider value={ {msg:this.stage.msg, hello:this.state.hello} }> <Son></Son> </Context.Provider> </> ) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Son.js import React from 'react' import GrandSon from './grandSon' class Son extends React.PureComponent { render() { return ( <> <div className="Son"> Son </div> <GrandSon></GrandSon> </> ) } } export default Son;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // grandSon.js import React from 'react' import {Context} from './App' class GrandSon extends React.PureComponent { render() { return ( <> <div className="GrandSon">GrandSon</div> <!-- 这里渲染App的数据 --> <Context.Consumer> { (value)=>{ return ( <> <div>{value.msg}</div> <div>{value.hello}</div> </> ) } } </Context.Consumer> </> ) } } export default GrandSon;
React 新版本函数组件跟hook
函数组件和类组件主要区别
函数组件没有生命周期
函数组件没有this
函数组件通过hook来完成各种操作
函数组件本身的函数体相当于render函数
props在函数的第一个参数接受
函数组件的使用
1 2 3 4 5 6 7 8 9 10 // App.js function App(){ function Son(){ return <div>Son</div> } return <> <div>App</div> <Son></Son> </> }
state的创建和更新- useState()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { useState } from 'react' function App (){ let [msg, setMsg] = useState("hello") function changeMsg(e){ setMsg("hello "+e) } return <div> {msg} <button onClick={changeMsg.bind(this,"work")}>change</button> </div> } export default App;
useReducer
useEffect
定义副作用
不传第二个参数 = componentDitMount 和 componentDidUpdate
第二个参数传空数组 = componentDidMount
第二个参数数组里放某个数据 = watch监听 || 更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function App(){ /** 当为监听时 vue-watch 默认是不会开始就执行的 useEffect 监听某个数据,开始就会执行一次 (didMount) */ useEffect(()={ //componentDitMount 和 componentDidUpdate }) useEffect(()={ // componentDidMount },[]) useEffect(()={ // componentDidUpdate },[msg]) return <div></div> } /** 在子组件中使用,表示的是卸载 */ useEffect(()={ // componentWillUnmount },[]) useEffect(()={ // componentDitMount 和 componentDidUpdate })
useMemo
让一段计算在开始运算一次,后续只有依赖的数据发生变化时才重新运算
作用:
1. 起类似于vue的一个计算属性的效果
1. 缓存一个数据,让其不会重新创建
1 2 3 4 5 6 7 8 9 10 function App(){ /** */ let all = useMemo(()={ return 0 },[msg]) return <div></div> }
useCallback
缓存一个方法,不会让方法每次都创建
其他hock
useRef——函数组件中使用ref 来获取DOM
useContext——更方便的解析context的provider的数据
1 2 3 4 5 6 // 父组件 首先创建context对象 export let context1 = React.createContext() // 子组件使用 useContext 获取 let SonContext = useContext(context1)
高阶组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // TestHoc.js import React from 'react' export default function TestHoc (UseCom){ return class extends React.Component { state = { a: 123 } render(){ return <> <UseCom a={this.state.a} {...this.props}></UseCom> </> } } }
使用例子
提供复用的数据和方法,给到组件的props,你可以把很多页面都有的一些逻辑操作提取出来混入
提供生命周期操作,比如写一个高阶组件形式的PureComponent
React - router
三个版本
React-router 服务端渲染使用
React-router-dom 浏览器端渲染使用
React-router-native React-native混合开发使用
React-router 使用步骤
通过BroserRouter或者HashRouter包裹要使用路由的根组件
使用Routes组件,定义路由显示区域
使用Router组件,定义具体路由规则
使用NavLink或者Link组件,定义调整链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import logo from './logo.svg' import './App.css' import { Routes, Route, NavLink, Link } from 'react-router-dom' import Page1 from "./Page1" import Page2 from "./Page2" function APP(){ return ( <div className="App"> <div>菜单</div> <!-- NavLink --> <NavLink to="/page1">page1</NavLink> <NavLink to="/page2">page2</NavLink> <!-- Link --> <Link to="/page1">page1</Link> <Link to="/page2">page2</Link> <Routes> <Route path="/page1" element={<Page1></Page1>}></Route> <!-- v5 --> <Route path="/page1" Component={Page2}></Route> </Routes> </div> ) }
react-router 提供的一些其他组件
Navigate——路由重定向
Outlet——嵌套路由的子路由显示处
React中没有vue那样的vue.use方法,react中使用一个插件,库,都是引入一个组件,然后把要使用该插件的部分包起来
动态路由和嵌套路由
1 2 3 4 5 6 import { Routes, Route, NavLink, Link } from 'react-router-dom' <Route path="/page2" element={<Page2/>}> <Route path="son1" element={<Page2Son1/>}></Route> <!-- "/page2/son1" --> <Route path="son2" element={<Page2Son2/>}></Route> <!-- "/page2/son2" --> </Route>
1 2 3 4 5 6 7 // page2.js // 嵌套路由在子路由需要定义Outlet 类似vue的RouterView import { Outlet } from "react-router-dom" <div> page2 <Outlet></Outlet> </div>
动态路由
1 <Route path="/page/:id" Component={Page3} ></Route>
1 2 3 4 5 6 7 import { useParams } from "react-route-dom" function Page3(){ let params = useParams() return <> </> }
1 2 3 4 5 6 7 8 import { useSearchParams } from "react-router-dom" function Page4(){ let [searchParams, setSearchParams] = useSearchParams(); console.log(searchParams.get("a"), setSearcjParams({a: 888})) return <> </> }
1 2 3 4 5 6 7 8 import { useLocation } from "react-router-dom" function Page5(){ let location = useLocation(); console.log(location.search) return <> </> }
js 控制跳转地址
v6——useNavigate 创建跳转方法,然后跳转
V5——this.props.history.push()
1 2 3 4 5 6 7 8 9 10 11 12 import { Outlet, useNavigate } from "react-route-dom" function Page6(){ let nav = useNavigate() return <> <button onClick={()=>{ nav("/page6?a=111",{ state:{aaa:"1111"} }) } }>跳转</button> <Outlet></Outlet> </> }
控制权限
1 <Route path="/page1" element={_token ? <Page1></Page1> : <Navigate to="/page4"></Navigate>}
异步路由( 路由懒加载)
React做异步路由,要配合到react本身的一个方法——lazy和一个组件suspense
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 懒加载 let LayPage4 = Lazy(() =>{ return import("./Page4")}) function App(){ <> <Suspense fallback={<h3>加载中。。</h3>}> <Routes> <Route path="/page4" Component={LayPage4}><Route> </Routes> </Suspense> </> }
React 状态管理
状态管理
react,没有专门的状态管理库,都是通用的js状态管理库,所以我们首先创建一个全局的数据储存和管理工具
通过其他工具,数据修改能触发react页面的更新
redux 创建仓库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // ./src/store/index.js import {legacy_createStore as createStore } from "redux" function reducer (state = {msg:"default"}, action){ // 具体修改数据的行为 switch (action.type){ case 'changeMes': state.meg = action.paylod; return { ...state } case 'resetMes': state.msg = "hello" return { ...state } default : return state } } let store = createStore(reducer) export default store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //App.js import store from './src/store/index' function App(props) { let storeApp = store.getState() console.log(storeApp) return ( <div className="App"> {storeApp.msg} <button onClick(()=>{ // 修改msg ;相当于上面的renducer的aciton参数 store.dispatch({ type: "changeMes" paylod: "hello word" }) console.log(storeApp) }</button>)>修改store</button> </div> ) } export default App;
触发修改并不能让页面数据响应式改变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // index.js import store from './store' //监听store修改,一旦修改重新渲染;但成本大,所以借助react-redux做关联 store.subscribe(()=>{ root.render( <React.StrictMode> <App /> </React.StrictMode> ); }) root.render( <React.StrictMode> <App /> </React.StrictMode> );
react-redux
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // ./src/store/index.js import {legacy_createStore as createStore, combineReducers} from "redux" function reducer1 (state = {msg:"default"}, action){ // 具体修改数据的行为 switch (action.type){ case 'changeMes': state.meg = action.paylod; return { ...state } case 'resetMes': state.msg = "hello" return { ...state } default : return state } } function reducer2 (state = { num:0 }, action){ // 具体修改数据的行为 switch (action.type){ case 'changeMes': state.num += 1 return { ...state } case 'resetMes': state.num = 10 return { ...state } default : return state } } // 分模块 let reducer = combineReducers({ reducer1, reducer2 }) let store = createStore(reducer) export default store
1 2 3 4 5 6 7 8 9 10 // index.js import { Provider } from 'react-redux' import { store } from "redux" root.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 //App.js import {connect} from 'react-redux' function App(props) { // props会多一个dispatch 和 msg console.log(props) return ( <div className="App"> {storeApp.msg} <button onClick(()=>{ // 修改msg数据 props.dispatch({ type: "changeMes", payload: "hello word" }) })>修改store</button> <button onClick(()=>{ // 修改msg数据 props.changeMes2() })>修改store</button> </div> ) } /** connect: 第一个参数:state的映射,那些state映射到该组件props(当前组件为App) 第二个参数方法映射,你要给props里边加那些方法 */ let ReduxApp = connect((state)=>{ // 将详细模块返回 return { state.reducer1.msg } }, ()=>{ return { changeMes2(){ dispatch({ type: "changeMes", payload: "hello world2" }) } } })(App) export default ReduxApp;
@reduxjs/tooikit (基于redux)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // ./src/store/index.js import {createSlice, configurStore} from '@reduxjs/tooikit'; let mesSlice = createSlice({ name: "mesSlice", initialState: { mes: "hello" }, reducers: { changeMes(state,action) { state.mes = action.payload } } }) let mesSlice = createSlice({ name: "numSlice", initialState: { num: 0 }, reducers: { addNum(state,action) { state.num += 1 } } }) let store = configureStore({ reducer: { mesReducer: mesSlice.reducer, numReducer: mesSlice.reducer } }) export let { changeMes } = mesSlice.actions; export let { addNum } = numSlice.actions; export default store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // app.js import store from "./store/index" import {connect} from 'react-redux' import {changeMes} from './store/index' function App(props) { // props会多一个dispatch 和 msg console.log(props) return ( <div className="App"> {storeApp.msg} <button onClick(()=>{ // 修改msg数据 props.dispatch({ type: "changeMes", payload: "hello word" }) })>修改store</button> <button onClick(()=>{ // 修改msg数据 props.changeMes2() })>修改store</button> </div> ) } /** connect: 第一个参数:state的映射,那些state映射到该组件props(当前组件为App) 第二个参数方法映射,你要给props里边加那些方法 */ let ReduxApp = connect((state)=>{ // 将详细模块返回 return { state.reducer1.msg } }, ()=>{ return { changeMes2(){ dispatch(changeMes("hello word")) } } })(App) export default ReduxApp;
React中的路由控制
根据配置生成路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // ./src/route/route.js import Page1 from "./page1" import Page2 from "./page2" import Page3 from "./page3" // 定义路由规则 export default [ { path: "/page1", component: Page1 }, { path: "/page2", component: Page2 }, { path: "/page3", component: Page3 }, ]
1 2 3 4 5 6 7 8 9 10 11 // App.js import routersArr from "./src/route/route" import createRoute from "./src/route/createRoute" function App(){ return ( <Routes> {createRoute()} </Routes> ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // ./src/route/createRoute.js import {Route} from "react-route-dom" export function createRoute(route){ return routesArr.map((item)=>{ // 递归实现子路由 if (item.children && item.children.length > 0){ return <Route path={item.path} key={item.path} Component={item.component}> {createRoute(item.children)} </Route> }else{ return <Route key={item.path} path={item.path} Component={item.component} } }) }
根据权限控制生成路由 ( 补充!)
路由传参
结合实际项目 react坑
基础知识
react的更新问题,react更新会重新执行react函数组件方法本身,并且子组件也会一起更新
这明显是不合理的,合理的更新机制是只有当这个组件自己的state或者props发生了改变,才进行更新,为了达到这个策略,vue和react分别做了两个解决方案
vue——源码里自动依赖收集
react——开发者自己去React.memo 包装组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 子组件Pager.js import React from "react" function Pager(){ return ( <div className="Pager"> <div>1</div> <div>2</div> <div>3</div> <div>4</div> </div> ) } // 使用React.memo() 只有当组件本身或props更新才会更新 export default React.memo(Pager)
react每次更新都会重新执行一次方法体。那么这个时候就会有两个问题。
当有对象或方法传递给子组件时,更新相当于内存里新建了一个对象或方法 。就会导致子组件props也会更新。
这个方法需要用useCallback()包裹;
1 2 3 let getClick = useCallback(()=>{ },[])
是对象并且和组件数据有关,可以用useMemo()包裹;如果死数据可以丢外层
react的state有个经典的闭包导致拿不到新数据的问题,常见于useEffect,useMemo,useCallback
缓存更新问题; useCallback/ useEffect/ useMemo 里的方法如果依赖了某个state数据,那么这个数据必须写进依赖组,否则不会更新( 副作用方案)
——不会更新
1 2 3 const getList = useCallback(()=>{ console.log(counter) },[])
——会更新
1 2 3 const getList = useCallback(()=>{ console.log(counter) },[counter])
副作用方案( useEffect)
React + TS 组件传值 porps 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React, {Component} from 'react' // 定义组件porps需要的值 interface IProps { // 父组件必须要带的值 name: string // 可选 address?: string } export default class Company extends Component<IPorps> { render(){ return ( <div> {this.props.name} <hr/> {this.props.address} </div> ) } }
案例 TodoList