持续不断的更新....,最新更新日期2024.05.13
梳理react18.2知识点总结
react项目
默认根文件是src/index.js
// src/index.js import React from 'react' import ReactDOM from 'react-dom/client'; import App from './App'; // 添加根节点 const root = ReactDOM.createRoot(document.getElementById('root')) // 普通添加 /** root.render( <App /> ) */ // 增加严格模式 root.render( <React.StrictMode> <App /> </React.StrictMode> )
<></>跟vue里面的template一样,不会在浏览器里面生成dom
作用:当你的dom结构里面不想某个结构在浏览器里面生成对应的dom,可以使用<></>
class组件相关知识
创建类组件
import React from 'react' class App extends React.Component { constructor (props){ super(props) this.state = {} } render() { return ( <div>App</div> ) } }
创建类组件state的两种方式
// 方式1 constructor里面创建 class App extends React.Component { constructor (props){ super(props) this.state = {} } } // 方式2 直接在外面创建,不要写constructor class App2 extends React.Component { state = {} }
setState后需要最新值做判断
/** React和Vue都支持异步更新DOM,这是因为它们都实现了一种叫做“虚拟DOM”的东西,并且在这之上有自己的更新机制。在React中,可以使用setState函数来更新状态,但是setState并不会立即更新DOM,它会把新的状态放入队列中,等待合适的时机再进行处理。如果你需要在状态更新后立即获取DOM的最新状态,你可以传递一个回调函数作为setState的第二个参数。*/ import React from "react"; class App extends React.Component { constructor() { super(); this.state = { name: 'React' } } // 绑定事件的方式 eventHandle = (e) => { e.preventDefault(); // 清除默认事件 this.setState( { name: 'Vue', }, () => { console.log(this.state.name); } ); }; render() { const { name } = this.state; return ( <div> <h2>Hello { name }!!</h2> <button onClick={this.eventHandle}>按钮</button> </div> ); } }
class事件绑定和修改值
class Parent extends React.Component { constructor() { super() this.state = { name: '' } } // 绑定事件的方式 eventHandle(str, e) { e.preventDefault() // 清除默认事件 this.setState({ name: e.target.value }) } render() { return( <div> <h2>Hello React!!</h2> {/* 方式1:传不了参数,传递参数就立即执行了 */} {/* <button onClick={ this.eventHandle }>按钮</button> */} {/* 方式2 */} <button onClick={ (e) => this.eventHandle('你好', e) }>按钮</button> {/* 方式3 */} {/* <button onClick={ this.eventHandle.bind(this, '你好!!') }>按钮</button> */} </div> ) } }
数组渲染
import React from 'react' class App extends React.Component { constructor (props){ super(props) this.state = { arr: [ { id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' } ] } } render() { // 方式1 /** const domEle = this.state.arr.map(item =>{ return <div key={item.id}><span>姓名: {item.name}</span></div> })*/ // 方式2 const domEle = [] this.state.arr.forEach(item => { domEle.push(<div key={item.id}><span>姓名: {item.name}</span></div>) }) return( <div>{domEle}</div> ) } }
总结:数组之类的循环只要有返回值都可以,没有返回值自己用对象或者数组接收再放进render函数里面渲染就可以了
ref的使用
获取组件本身的DOM
import React, {createRef} from "react" class App extends React.Component { constructor() { super() this.state = { name: '' } // 官方文档标注这样使用过时了 // this.domRef = React.createRef() // 要这样使用 this.domRef = createRef() } handleClick() { this.domRef.current.textContent = 'Hello Vue!!' } render() { return( <div> <div ref={this.domRef}>Hello React !!</div> <button onClick={() => this.handleClick()}>获取dom内容</button> </div> ) } }
调用子组件DOM和方法
import React, { Component, createRef } from 'react' import ChildCom from './ChildCom' // 父组件 class App extends React.Component { constructor(props) { super() this.comRef = createRef() } handleClick = () => { // 调用子组件方法 this.comRef.current.test() // 改变子组件DOM内容 this.comRef.current.domRef.current.textContent = '已经改了子组件内容' } render() { return( <div> <ChildCom ref={this.comRef} /> <button onClick={this.handleClick}>调用子组件的方法</button> </div> ) } }
// 子组件 import React, { Component, createRef } from 'react' export default class ChildCom extends Component { const domRef = createRef() test() { console.log('子组件特有的方法'); } render() { return ( <div> <div ref={domRef}>这是子组件</div> </div> ) } }
class父子之间组件传值
import React from "react" class App extends React.Component { state = { name: 'alan' } handleChange(value) { this.setState({ name: value }) } render() { return( <div> <Children name={this.state.name} handleChange={(value) => this.handleChange(value)}></Children> </div> ) } } class Children extends React.Component { constructor(props) { super(props) this.state = {} } handleChange() { this.props.handleChange('ben') } render() { return ( <div> <div>名字是:{this.props.name}</div> <button onClick={() => this.handleChange()}>改变</button> </div> ) } } export default App
子组件类型检查和接收默认值
父组件传值
import React from "react" import Children from './Children' class App extends React.Component { constructor() { // 子组件如果定义了super(),没有传入props的话,this.props就是个空对象;当然也可以不申明constructor,不申明react默认就实现了this.props super(); this.state = { user: { username: 'alan', age:18 } } } componentDidMount() { // 修改值 const state = {...this.state} state.user.age = 28 this.setState({ user: state.user }) } render() { return ( <div> <Children userInfo={this.state.user}></Children> </div> ); } }
子组件接收并验证类型
需要安装一个插件: npm i prop-types prop-types版本15.8.1
// 引入 import propTypes from 'prop-types' class Children extends React.Component{ //方式2 defaultProps 是默认传值 static defaultProps = { userInfo: { username: 'Alan', age: 18 }, sex: '女' } constructor(props) { super(props) } render() { const { username, age } = this.props.userInfo console.log('sex', this.props.sex); return ( <div> <h3>信息展示一</h3> <p>姓名:{ username }</p> <p>年龄:{ age }</p> <p>性别:{ this.props.sex }</p> </div> ) } } // 方式1 /* ClassWay.defaultProps = { userInfo: { username: 'Alan', age: 18 }, sex: '女', } */ // 函数组件写的全一点 /** * 类型检查 */ ClassWay.propTypes = { userInfo: propTypes.object, sex: propTypes.string }
生命周期
class App extends React.Component { // 挂载组件时调用的钩子,可以请求ajax,定义定时器等 componentDidMount() {} // 卸载组件时调用的钩子,清理定时器 componentWillUnmount() {} // 组件更新后调用的钩子 componentDidUpdate() {} }
createContext使用
这里只写如何在class组件下使用,一些差别看函数式组件说明
// context/index.js import React from 'react'; const MyContext = React.createContext() // 修改名字 MyContext.displayName = 'counter' export default MyContext;
在祖先组件定义
import React, { Component } from "react"; import MyContext from "../context"; export default class Parent extends Component { const {Provier} = MyContext render() { return( <Provier value={{name: 'alan'}}> <ChildCom></ChildCom> </Provier> ) } }
在后代子孙组件中获取
import React, { Component } from 'react' import MyContext from '../utils' export default class ChildCom3 extends Component { // 方式二 static contextType = MyContext; render() { return ( <div> <h3>展示信息</h3> {/** 方式一 */} {/* <MyContext.Consumer> {(context) => { console.log('context', context); return( <div>获取的性别: {context.name}</div> ) }} </MyContext.Consumer> */} {/* 方式二 */} <div>获取的性别:{this.context.name}</div> </div> ) } }
funciton函数组件
声明双向数据绑定的方式
import { useState } from 'react' export default function App(){ const [num, setNum] = useState(0) const addNum = () => { setNum(num + 1) } return ( <> <div>数值:{num}</div> <button onClick={addNum}>进行+1</button> </> ) }
添加副作用useEffect
// 刚加载组件需要获取后端数据、定时器等等 import { useEffect, useState } from 'react' // 如何实现一个定时器的 hook function App() { const [ count, setCount ] = useState(0) /* useEffect(() => { const timer = setInterval(() => { setCount(count => count + 1) }, 1000) return () => clearInterval(timer) }, [count]) */ useEffect(() => { const timer = setTimeout(() => { setCount(count => count + 1); }, 1000); return () => clearInterval(timer); }, [count]) return ( <div> <h3>打印结果</h3> <div>{ count }</div> </div> ) }
ref的使用
获取组件本身的DOM
import { useRef } from 'react' function App() { const domRef = useRef() const handleClick = () => { domRef.current.textContent = 'Hello Vue!!' } return( <div> <div ref={domRef}>Hello React !!</div> <button onClick={handleClick}>获取dom内容</button> </div> ) }
获取子组件实例的dom
import { useRef } from 'react' // 父组件 export default function App() { const comRef = useRef() useEffect(() => { if(comRef.current) { // 获取子组件某个DOM comRef.current.focus() } }, [comRef]) return( <div> <ChildCom2 ref={comRef} /> </div> ) }
// 子组件 import { forwardRef, useState } from 'react' function ChildCom2(props, ref) { const [value, setValue] = useState('') return ( <div> <div>输入框聚焦</div> <div> <input type="text" value={value} onChange={(e) => setValue(e.target.value)} ref={ref} /> </div> </div> ) } export default forwardRef(ChildCom2)
获取子组件实例方法
// 父组件,关键代码 import {useRef} from 'react' const comRef = useRef() function handleClick() { // 调用子组件方法 comRef.current.testHadle() } return( <div> <div className='marginButton'> <FunChild ref={comRef} /> </div> <button onClick={handleClick}>获取子组件方法</button> </div> )
// 子组件,关键代码 import {useImperativeHandle, forwardRef} from 'react' function FunChild(props, ref) { function handleTest() { console.log('这是子组件方法') } useImperativeHandle(ref, () => ({ testHadle: handleTest })) return( <div> 这是子组件 </div> ) } export default forwardRef(FunChild)
获取子组件实例和方法
// 父组件关键代码 import { useRef } from 'react' export default function Parent() { const comRef = useRef() function handleClick () { // 调用子组件方法 comRef.current.testHadle() // 通过调用子组件的方法获取子组件的DOM comRef.current.click() } return( <div> <h4>获取子组件的方式</h4> <div className='marginButton'> <FunChild ref={comRef} /> </div> <button onClick={handleClick}>获取子组件DOM和方法</button> </div> ) }
// FunChild.jsx import { forwardRef, useImperativeHandle, useRef } from 'react' function FunChild(props, ref) { const childRef = useRef() // 暴露出去的方法 function getSomeText() { console.log('调用子组件的方法'); } useImperativeHandle(ref, () => ({ testHadle: getSomeText, click: function () { childRef.current.focus() } })) return ( <div> <div>输入框聚焦</div> <div> <input type="text" ref={childRef} /> </div> </div> ) } export default forwardRef(FunChild)
自定义Hooks
src/components/myHooks.js
import {useState} from 'react' export default myHooks() { let [title, setTitle] = useState('vue') return { title, setTitle } }
src/App.js
import { useState } from 'react' import useMyHook from './components/useMyHook' const {title, setTile} = useMyHook() const [value, setValue] = useState('') function handleChange(e) { setValue(e.target.value) } function handleClick() { setTitle(value) setValue('') } return( <div> <div>{ title }</div> <input type="text" value={ value } onChange={handleChange}></input> <button onClick={handleClick}>提交</button> </div> )
子组件类型检查和接收默认值
父组件传值
import ChildCom from './components/ChildCom' export default function Person { return( <div> <ChildCom name="alan-ben" age={18} score={[98, 97, 100]} /> </div> ) }
子组件接收并验证类型
import React from 'react' import PropTypes from 'prop-types' export default function ChildCom (props) { const score = props.score.map((value,index) => { return( <span key={index} style={{padding: '4px'}}>{value}</span> ) }) return ( <div> <div>名字: {props.name}</div> <div>年龄: {props.age}</div> <div>性别: {props.sex}</div> <div> 各科成绩是:{score} </div> <div>{props.children}</div> </div> } // 设置默认值 ChildCom.defaultProps = { age: 20, sex: '男' // 如果不传的话这个就是默认值 score: [78] } // 数据类型验证 ChildCom.propTypes = { // 类型验证 age: PropTypes.number, sex: PropTypes.string, // 必须要有的 children: PropTypes.element.isRequired, // 验证名字里面要有alan /** * * @param {*} props 整体的 props 对象 {name:... , age :...} * @param {*} propName 当前验证的 props 属性 name * @param {*} componentName 组件名 */ name: function (props, propName, componentName) { // 如果开头不是alan的话,验证不通过 if (!/alan-/.test(props[propName])) { return new Error( `组件名:${componentName}的属性${propName}验证不通过,需要的开头是'alan-'` ) } }, score: PropTypes.array }
useContext的使用
简单一句话概括 Context,那就是解决组件之间数据共享的问题,避免一层一层的传递。
获取createContext默认值
获取createContext默认值和Provier定义的value值两者是互斥的,如果定义了Provier的值,createContext默认值不在子组件声明Consumer就会被Provier的值覆盖,反之声明了就会把Provier的值覆盖
// context/index.js import { createContext, useContext } from 'react'; let ThemeContext = null export function useMyCrateContext() { ThemeContext = createContext({ name: 'alan', age: 19 }); return { ThemeContext } }
在App组件
import ChildCom from './ChildCom' export default function App() { return( <ChildCom></ChildCom> ) }
在对应组件引入
// 关键代码 import { useMyCrateContext } from './context/index.js' export default function ChildCom() { const {ThemeContext: {Consumer}} = useMyCrateContext() return( <div> <Consumer>{(context) => { return ( <div> <div>定义初始值的数据</div> <div>名字: {context.name}</div> <div>年龄: {context.age}</div> </div> ) }}</Consumer> </div> ) }
获取Provier定义值
// context/index.js import { createContext, useContext } from 'react'; let ThemeContext = null export function useMyCrateContext() { ThemeContext = createContext(null); return { ThemeContext } } export function useMyContext() { const theme = useContext(ThemeContext); return { theme } }
在App组件
// 这个只是为了说明问题,可以不引入的 import { useMyCrateContext } from './context/index.js' import ChildCom from './ChildCom' const { ThemeContext } = useMyCrateContext() export default function App() { return( <ThemeContext.Provider value={{sex: '男'}}> <ChildCom /> </ThemeContext.Provider> ) }
对应组件引入
// 关键代码 import { useMyContext } from './context/index.js' export default function ChildCom() { const {theme} = useMyContext() return( <div> 性别: {theme.sex} </div> ) }
Portal传送门
/** * Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。 * Portal 理解成传送门 * 跟Vue3里面的Teleport效用一样 */ import { useState } from 'react'; import { createPortal } from 'react-dom' import Modal from './components/Modal'; function MyPortals(props) { const [isShow, setIsShow] = useState(false) return ( <div style={{position: 'relative'}} // 测试事件冒泡,会不会影响到Modal组件 /** * 官网介绍:尽管 portal 可以被放置在 DOM 树中的任何地方,但在任何其他方面,其行为和普通的 React 子节点行为一致。 * 由于 portal 仍存在于 React 树, 且与 DOM 树 中的位置无关,那么无论其子节点是否是 portal,像 context 这样的功能特性都是不变的。 */ onClick={() => console.log('这是Portals/index.jsx的事件')} > <button onClick={() => setIsShow(!isShow)}>显示/隐藏</button> { // 把Modal子节点放置在body下面 // createPortal(要渲染的节点, 指定当前这段jsx渲染到哪里), 跟Vue3的Teleport一样的概念 isShow ? createPortal(<Modal />, document.body) : null } </div> ); } export default MyPortals;
子组件
import React from 'react' export default function Modal() { return ( <div style={{ width : "450px", height : "250px", border : "1px solid", position : "absolute", left : "calc(50% - 225px)", top : "calc(50% - 125px)", textAlign : "center", lineHeight : "250px" }}>模态框</div> ) }
组件渲染优化
类组件优化方式
某些值没有达到改变的条件或者没有必要渲染,但用户点击了;整个组件仍然是重新渲染了的。显然,这一次渲染是没有必要的。
例子:
import React from 'react' export default class App extends React.Component { constructor() { super(); this.state = { counter: 1 } } render() { console.log("App 渲染了"); return ( <div> <h1>App 组件</h1> <div>{this.state.counter}</div> <button onClick={() => this.setState({ counter : 1 })}>+1</button> </div> ) } }
方式一
使用shouldComponentUpdate来决定是否渲染
import React, { Component } from 'react' import { objectEqual } from "../utils/tools" export default class App extends Component { constructor() { super(); this.state = { counter: 1 } } /** * * @param {*} nextProps 新的 props * @param {*} nextState 新的 state * @returns */ shouldComponentUpdate(nextProps, nextState) { // shouldComponentUpdate会根据返回值来决定是否重新渲染 // 默认是 true,要重新渲染 // 如果返回 false,则不会重新渲染 // 我们就需要将当前的 props 和 state 与新的 props 和 state 进行一个比较 if (objectEqual(this.props, nextProps) && objectEqual(this.state, nextState)) { // 如果新旧 props 和 state 都是相同的,那么就返回 false,不需要重新渲染 return false; } return true; } render() { console.log("ClassOptimize渲染了"); return ( <div> <h1>ClassOptimize 组件</h1> <div>{this.state.counter}</div> <button onClick={() => this.setState({ counter: Math.floor(Math.random() * 3 + 1) })}>+1</button> </div> ) } }
/utils/tools.js
/** * 对两个对象进行一个浅比较,看是否相等 * obj1 * obj2 * 返回布尔值 true 代表两个对象相等, false 代表不想等 */ export function objectEqual(obj1, obj2){ for(let prop in obj1){ if(!Object.is(obj1[prop],obj2[prop])){ // 进入此 if,说明有属性值不相等 // 只要有一个不相等,那么就应该判断两个对象不等 return false; } } return true; }
方式二
使用PureComponent跳过不必要的重新渲染,如果值相同就不会渲染
import React, { PureComponent } from 'react' export default class App extends PureComponent { constructor() { super(); this.state = { counter: 1 } } render() { console.log("ClassOptimize渲染了"); return ( <div> <h1>ClassOptimize 组件</h1> <div>{this.state.counter}</div> <button onClick={() => this.setState({ counter: 1 })}>+1</button> </div> ) } }
方式三:扩展
import React, { PureComponent } from 'react' export default class ClassOptimize extends PureComponent { constructor() { super(); this.state = { stu: ['张三', '李四'] } } handleClick = () => { // 新增用户 // 这样新增是没用的,视图不会更新,PureComponent进行的是浅层比较,它比较的是地址,看到地址一样就直接返回false阻止视图更新 /* this.state.stu.push('王五') this.setState({ stu: this.state.stu }) */ // 这样新增 const arr = [...this.state.stu] arr.push('王五') this.setState({ stu: arr }) } render() { const li = this.state.stu.map((item, index) => (<li key={index}>姓名:{item}</li>)) return ( <div> <h1>ClassOptimize 组件</h1> <button onClick={this.handleClick}>新增用户</button> <ul> {li} </ul> </div> ) } }
函数组件优化方式
函数组件赋值相同的值视图不会渲染
import {useState} from 'react' export default function App() { const [counter, setCounter] = useState(1) console.log('App组件渲染了'); return( <div> <h1>{counter}</h1> <button onClick={() => setCounter(1)}>+1</button> </div> ) }
使用memo
memo避免父组件更新时,子组件跟着更新
示例:在 App 组件内部修改 counter1,然后 counter2 作为 porps 传递给子组件,此时我们修改 counter1 的状态时,子组件也会跟着更新,原因很简单,因为父组件更新了,那你子组件当然要重新更新。
import ChildCom from './components/ChildCom' import {uesState} from 'react' export default function App() { const [counter, setCounter] = useState(1) const [counter2, setCounter2] = useState(1) console.log("App 渲染了") return ( <div> <div>{counter}</div> <button onClick={() => setCounter1(counter + 1)}>+1</button> <ChildCom1 counter={counter2} setCounter={setCounter2} /> </div> ); }
import React from "react" function ChildCom(props) { console.log("ChildCom渲染了") return ( <div> <div>ChildCom1</div> <div>{props.counter}</div> </div> ); } export default ChildCom;
子组件的更新是没有必要的,因为传递给子组件的 props 并没有发生变化,我们就可以使用 React.memo,之后我们再更新 counter 时,由于传递给子组件的 counter2 这个 props 属性并没有变化,因此子组件不会更新。
import React from "react" function ChildCom(props) { console.log("ChildCom渲染了") return ( <div> <div>ChildCom</div> <div>{props.counter}</div> </div> ); } export default React.memo(ChildCom);
注意:默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
例子:
import React, { useState } from 'react'; import ChildCom1 from "./components/ChildCom1" function App() { const [counter1, setCounter1] = useState(2); const [stu, setStu] = useState(["张三","李四"]); console.log("App 渲染了"); function clickHandle(){ setCounter1(counter1 + 1); stu.push("王武"); setStu(stu) } return ( <div> <div>{counter1}</div> <button onClick={clickHandle}>+1</button> <ChildCom1 stu={stu} setStu={setStu} /> </div> ); } export default App;
import React, {memo} from "react" function ChildCom1(props) { console.log("ChildCom1 渲染了") const lis = props.stu.map((it,index)=>(<li key={index}>{it}</li>)) return ( <div> <div>ChildCom1</div> <ul>{lis}</ul> </div> ); } export default memo(ChildCom1);
和我们之前的 PureComponent 所举的例子很相似,由于是在原来的数组上面进行的修改,数组的地址并没有发生变化,因此 React.memo 返回的组件并不会更新。
解决这个问题也很简单,直接返回一个新的数组,例如:
function clickHandle(){ setCounter1(counter1 + 1); const arr = [...stu]; arr.push("王武"); setStu(arr) }
另外,在使用 React.memo 的时候还支持传入第二个自定义的比较函数参数,例如:
function MyComponent(props) { /* 使用 props 渲染 */ } function areEqual(prevProps, nextProps) { /* 如果把 nextProps 传入 render 方法的返回结果与 将 prevProps 传入 render 方法的返回结果一致则返回 true, 否则返回 false */ } export default React.memo(MyComponent, areEqual);
使用useCallback
useCallback主要就是对函数进行缓存,useCallBack这个Hooks主要是解决React.memo不能缓存事件
的问题
useCallBack(fn, dependencies) :fn想要缓存的函数,dependencies有关是否更新 fn 的所有响应式值的一个列表
比如:App组件传递了一个事件函数给ChildCom2、ChildCom3,只要其中一个子组件调用这个事件就会影响到另一个子组件重新渲染一次,React.memo可以解决传值问题,但解决不了传递事件的问题
示例:
import { useState, useCallback } from 'react' import ChildCom2 from './components/ChildCom2' import ChildCom3 from './components/ChildCom3' import styles from "./styles.module.css" export default function UseCallBackOptimize() { // 这是UseCallBackOptimize组件自身维护的状态 const [counter, setCounter] = useState(0); // 这是传递给ChildCom2组件的数据 const [counter1, setCounter1] = useState(0); // 这是传递给ChildCom3组件的数据 const [counter2, setCounter2] = useState(0); //之所以子组件使用了memo后都无法阻止子组件之间互相影响,是因为每次重新渲染的时候都会生成一个圈新的test函数 function test() { console.log("test"); } console.log("UseCallBackOptimize组件渲染了") return ( <div className={styles.container}> <div className={styles.btnContainer}> <div>{counter}</div> {/*点击修改数字时,ChildCom2和ChildCom3组件都会更新,原因看test上面*/} <button onClick={() => setCounter(counter + 1)}>+1</button> </div> <div className={styles.childComContainer}> <ChildCom2 counter={counter1} setCounter={setCounter1} test={test}/> <ChildCom3 counter={counter2} setCounter={setCounter2} test={test}/> </div> </div> ); }
import { memo } from "react" function ChildCom2(props) { // 该子组件维护了一个自身的状态 // const [counter, setCounter] = useState(0); console.log("ChildCom2 渲染了") return ( <div style={{ width: "200px", height: "100px", border: "1px solid" }}> ChildCom2 <div>{props.counter}</div> <button onClick={() => props.setCounter(props.counter + 1)}>+1</button> <button onClick={() => props.test()}>test</button> </div> ); } export default memo(ChildCom2);
import { memo } from "react" function ChildCom3(props) { // 该子组件维护了一个自身的状态 // const [counter, setCounter] = useState(0); console.log("ChildCom3 渲染了") return ( <div style={{ width: "200px", height: "100px", border: "1px solid" }}> ChildCom3 <div>{props.counter}</div> <button onClick={() => props.setCounter(props.counter + 1)}>+1</button> <button onClick={() => props.test()}>test</button> </div> ); } export default memo(ChildCom3);
本来调用ChildCom2的函数ChildCom3是不应该重新渲染的,使用useCallBack可以解决这个问题,两个子组件什么都不变的
import { useState, useCallback } from 'react' import ChildCom2 from './components/ChildCom2' import ChildCom3 from './components/ChildCom3' import styles from "./styles.module.css" export default function UseCallBackOptimize() { // 这是UseCallBackOptimize组件自身维护的状态 const [counter, setCounter] = useState(0); // 这是传递给ChildCom2组件的数据 const [counter1, setCounter1] = useState(0); // 这是传递给ChildCom3组件的数据 const [counter2, setCounter2] = useState(0); //todo 之所以子组件使用了memo后都无法阻止子组件之间互相影响,是因为每次重新渲染的时候都会生成一个圈新的test函数 /* function test() { console.log("test"); } */ // 使用了 useCallback 之后,针对test函数就做了一个缓存,如果依赖某个参数如counter,就在参数2[]里面传入进去 const newTest = useCallback(function test() { console.log("test"); }, []) console.log("UseCallBackOptimize组件渲染了") return ( <div className={styles.container}> <div className={styles.btnContainer}> <div>{counter}</div> <button onClick={() => setCounter(counter + 1)}>+1</button> </div> <div className={styles.childComContainer}> <ChildCom2 counter={counter1} setCounter={setCounter1} test={newTest}/> <ChildCom3 counter={counter2} setCounter={setCounter2} test={newTest}/> </div> </div> ); }
使用useMemo
useMemo的主要作用就是缓存值的,某些时候,组件中某些值需要根据状态进行一个二次计算(类似于 Vue 中的计算属性),由于函数组件一旦重新渲染,就会重新执行整个函数,这就导致之前的二次计算也会重新执行一次
import { useMemo, useState } from 'react' export default function UseMemoOptimize() { // 维护两个状态 const [counter, setCounter] = useState(1); const [val, setVal] = useState(""); /** * 每次改变其它值的时候,都会重新调用该函数 */ function getCount() { console.log("getCount函数调用了") return counter + 100; } return ( <div> <h1>总数:{getCount()}</h1> <button onClick={() => setCounter(counter + 1)}>+1</button> <input value={val} type="text" onChange={e=>setVal(e.target.value)} /> </div> ); }
useMemo 计算结果是 return 回来的值, 主要用于缓存计算结果的值。应用场景如:需要进行二次计算的状态
import { useMemo, useState } from 'react' export default function UseMemoOptimize() { // 维护两个状态 const [counter, setCounter] = useState(1); const [val, setVal] = useState(""); //! useCallBack为什么无法解决这个问题? /** *! 面试题:useMemo 和 useCallback 的区别及使用场景? * useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据。 *todo 共同作用: * 仅仅依赖数据发生变化,才会去更新缓存。 *todo 两者区别:useMemo跟vue的computed差不多,useCallback跟vue的watch差不多 *? 1. useMemo 计算结果是 return 回来的值, 主要用于缓存计算结果的值。应用场景如:需要进行二次计算的状态 *? 2. useCallback 计算结果是函数, 主要用于缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化, *? 整个组件都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。 */ /* const newCounterNum = useCallback(function getCount() { console.log("getCount函数调用了") return counter + 100; },[counter]) */ /** * 在之前的状态的基础上做一个二次计算 * @returns 返回一个缓存值 */ const countNum = useMemo(function getCount() { console.log("getCount函数调用了") return counter + 100; },[counter]) return ( <div> {/* <h1>总数:{ newCounterNum() }</h1> */} <h1>总数:{ countNum }</h1> <button onClick={() => setCounter(counter + 1)}>+1</button> <input value={val} type="text" onChange={e=>setVal(e.target.value)} /> </div> ); }
高阶组件
有部分组件有公共的逻辑,就抽离公共的逻辑。简单示例:
高阶组件
// withTimer export default function withInfo (Com) { return function NewCom(props) { // 必须传序列号,对应的是菜品 if(!props.serial) { return new Error('请传入序列号') } // 这个可以是后台返回的数据 const foodMenu = [ { id: 1, food: '辣椒炒肉' }, { id: 2, food: '青椒炒蛋' }, { id: 3, food: '苦瓜炒肉' } ] const dotMenu = foodMenu.filter(item => item.id === props.serial) let backMenu = { ...props } if(dotMenu && dotMenu.length > 0) { backMenu.food = dotMenu[0].food } return <Com {...backMenu} /> } }
父组件
/** * 比如说现在有个餐厅,餐厅有固定餐牌,客人点了什么直接输入序列号,餐厅获取客人的餐桌号和点的餐单等信息 */ import React from 'react' import withInfo from './HOC/withInfo' import ChildCom3 from './components/ChildCom3' import ChildCom4 from './components/ChildCom4' const NewChildCom3 = withInfo(ChildCom3) const NewChildCom4 = withInfo(ChildCom4) export default function MyHocStudy() { const client = [ { userId: 1, name: '张三', serial: 1, table: 3 }, { userId: 1, name: '李四', serial: 3, table: 2 } ] return ( <div> <NewChildCom3 {...client[0]} /> <NewChildCom4 {...client[1]} /> </div> ) }
子组件1
import React from 'react' export default function ChildCom3(props) { console.log('cm3', props); return ( <div> <h3>ChildCom3-这是在包间-服务等不一样</h3> <div> <div>餐品: {props.food}</div> </div> </div> ) }
子组件2
import React from 'react' export default function ChildCom3(props) { console.log('cm4', props); return ( <div> <h3>ChildCom4-这是在大厅-服务等不一样</h3> <div> <div>餐品: {props.food}</div> </div> </div> ) }
react-router-dom
先安装npm i react-router-dom@6.x
导航方式
history方式
// src/index.js import { BrowserRouter } from 'react-router-dom' import App from './App' root.render( <BrowserRouter> <App></App> </BrowserRouter> )
hash方式
// src/index.js import { HashRouter } from 'react-router-dom' import App from './App' root.render( <HashRouter> <App></App> </HashRouter> )
声明方式
方式1
常规路由
// App.js import {NavLink, Routes Route, Navigate} from 'react-router-dom' import About from './components/About.jsx' import Home from './components/Home.jsx' export default function App() { return( <div> {/*导航*/} <div> <NavLink to="/home"></NavLink> <Home to="/about"/> </div> <div> <Routes> <Route path="/home" element={<Home />} /> <Route path="/about" element={<About />} /> {/*重定向页面*/} <Route path="/" element={<Navigate replace to="/home" />} /> </Routes> </div> </div> ) }
// /src/components/Home.jsx export default function Home() { return( <div> Home </div> ) }
// /src/components/About.jsx export default function About() { return( <div> Home </div> ) }
// /src/components/About.jsx import {NavLink,Routes Route, Navigate} from 'react-router-dom' export default function About() { return( <div> <h1 className="page-header">使用说明</h1> <p>通过此系统来熟悉 react 以及 react router 的使用</p> </div> ) }
嵌套子路由1
只标注核心代码,其它跟上面一样
// App.js变化 <Routes> <Route path="/home" element={<Home />} /> <Route path="/about" element={<About />} /> {/*嵌套子路由*/} <Route path="/about/*" element={<About />} /> {/*重定向页面*/} <Route path="/" element={<Navigate replace to="/home" />} /> </Routes>
// /src/components/About.jsx import React from 'react' import { Route, NavLink, Routes, Navigate } from 'react-router-dom' import Email from './Email' import Tel from './Tel' export default function About() { return ( <div className="about container"> <h1 className="page-header">使用说明</h1> <p>通过此系统来熟悉 react 以及 react router 的使用</p> <div> <NavLink to="/about/email" className="navigation">邮箱</NavLink> <NavLink to="/about/tel" className="navigation">电话</NavLink> </div> <div> <Routes> <Route path='' element={<Navigate to='email' replace />} /> <Route path='email' element={<Email />}/> <Route path='tel' element={<Tel />} /> </Routes> </div> </div> ) }
嵌套子路由2
import { NavLink, Route, Routes, Navigate } from 'react-router-dom' import './css/App.css' import Home from './views/Home'; import About from './views/About'; import Email from './components/Email'; import Tel from './components/Tel'; function App() { return ( <div className="App"> <div> <NavLink to="/home" className="navigation">首页</NavLink> <NavLink to="/about" className="navigation">关于我</NavLink> </div> <Routes> <Route path='/home' element={ <Home /> } /> <Route path='/about/*' element={ <About /> } > <Route path='email' element={ <Email /> } /> <Route path='tel' element={ <Tel /> } /> <Route path='' element={ <Navigate to="email" replace /> } /> </Route> <Route path='/' element={ <Navigate to="/home" replace /> } /> </Routes> </div> ); } export default App;
// /src/components/About.jsx import React from 'react' import { Outlet, NavLink } from 'react-router-dom' export default function About() { return ( <div className="about container"> <h1 className="page-header">使用说明</h1> <p>通过此系统来熟悉 react 以及 react router 的使用</p> <NavLink to="/about/email" className="navigation">邮箱</NavLink> <NavLink to="/about/tel" className="navigation">电话</NavLink> <Outlet /> </div> ) }
引入对应的子路由文件
就不一一引入了,直接写在一起
// /src/components/Tel.jsx function Tel(props) { return ( <div> 联系方式: 138-xxx-xxx </div> ); } export default Tel; // /src/components/Email.jsx function Email(props) { return ( <div> 邮箱: xxx@qq.com </div> ); } export default Email;
方式2
常规路由和嵌套子路由
跟vue的使用基本没差别
// router/index.js import { useRoutes, Navigate } from 'react-router-dom' import Home from '../components/Home' import About from '../components/About' import Email from '../components/Email' import Tel from '../components/Tel' function Router(props) { return useRoutes([ { path: '/home', element: <Home /> }, { path: '/about', element: <About />, children: [ { path: 'email', element: <Email /> }, { path: 'tel', element: <Tel /> }, { // 重定向到那个页面 path: '', element: <Navigate replace to="email" /> } ] }, { // 重定向到那个页面 path: '/', element: <Navigate replace to="/home" /> }, ]) } export default Router;
// App.js import Router from './router/routes'; import { NavLink } from 'react-router-dom' import About from './components/About.jsx' import Home from './components/Home.jsx' export default function App() { return( <div> {/*导航*/} <div> <NavLink to="/home"></NavLink> <Home to="/about"/> </div> <div> {<Router />} </div> </div> ) }
子路由页面显示
// src/components/About.jsx import { Outlet, NavLink } from 'react-router-dom' export default function About() { return ( <div className="about container"> <h1 className="page-header">使用说明</h1> <p>通过此系统来熟悉 react 以及 react router 的使用</p> <NavLink to="/about/email" className="navigation">邮箱</NavLink> <NavLink to="/about/tel" className="navigation">电话</NavLink> {/*Outlet跟vue里面的<router-view />是一样的功能*/} <Outlet /> </div> ) }
...在src/components文件夹下面定义对应的Tel、Email页面
路由声明使用懒加载
// App.js import { useEffect, lazy, Suspense } from 'react' import { NavLink, Route, Routes, Navigate, useLocation } from 'react-router-dom' import Loading from './components/Loading/Loading' import './css/App.css' const Home = lazy(() => import('./views/Home')) const About = lazy(() => import('./views/About')) const UseRef = lazy(() => import('./views/UseRef')) const MyRef = lazy(() => import('./views/UseRef/MyRef')) const TeacherRef = lazy(() => import('./views/UseRef/TeacherRef')) function App() { const location = useLocation() useEffect(() => { // document.title = location.state.title if (location.state) { document.title = location.state.title } else { document.title = '组件验证' } }, [location.state]) return ( <div className="App"> <div> <NavLink to="/home" state={{title: '组件验证'}} className="navigation">组件验证</NavLink> <NavLink to="/about" state={{title: '高阶组件'}} className="navigation">高阶组件</NavLink> <NavLink to="/use-ref" state={{title: 'Ref使用方式'}} className="navigation">Ref使用方式</NavLink> </div> <Routes> <Route path='/home' element={<Suspense fallback={<Loading />}><Home /></Suspense>} /> <Route path='/about' element={<Suspense fallback={<Loading />}><About /></Suspense>} /> <Route path='/use-ref/*' element={<Suspense fallback={<Loading />}><UseRef /></Suspense>}> <Route path='myRef' element={<Suspense fallback={<MyRef />}><About /></Suspense>} /> <Route path='teacherRef' element={<Suspense fallback={<Loading />}><TeacherRef /></Suspense>} /> <Route path='' element={ <Navigate to="myRef" replace /> } /> </Route> <Route path='/' element={ <Navigate to="/home" replace /> } /> </Routes> </div> ); } export default App;
// Loading.js import React from 'react' import { Spin } from 'antd' export default function Loading() { return ( <Spin tip="Loading"> <div className="content" /> </Spin> ) }
路由跳转方式
声明式跳转
import { NavLink } from 'react-react-dom' // ...省略相关代码 // NavLink生成的是a标签 return( <div> <NavLink to="/home">首页</NavLink> </div> )
编程式跳转
import { useNavigate } from 'react-react-dom' // ...省略相关代码 const navigate = useNavigate() return( <div> <div onClick={() =>navigate('/home') }>去首页</div> </div> )
路由传参
import { useNavigate } from 'react-router-dom' // ...省略相关代码 const navigate = useNavigate() // 返回上一级 navigate(-1) // 去对应的用户详情 navigate(`/user/{id}`) // 如果需要类比隐式传参,可以添加参数state navigate('/home', { state: { alert: "删除学生成功", type: "info" } }) // 如果需要类比 history.replace, 可以添加参数replace为true navigate('/home', { replace: true }) // 同时 link 也添加了单独的参数 state const state = { alert: "删除学生成功", type: "info" } <navLink to="/home" state={state} />
获取路由中的参数
获取prams参数
import { useParams } from 'react-router-dom' // ...省略相关代码 const { id } = useParams()
获取query参数
import { useLocation } from 'react-router-dom' // 获取隐式传参 const location = useLocation() if(location) { //返回参数location.state ...做响应的逻辑判断 }
react-redux+@reduxjs/toolkit
先安装npm i react-redux@9.1.0 和 @reduxjs/toolkit@2.2.3
项目入口页
// src/index.js import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { BrowserRouter } from "react-router-dom"; import store from './redux/store'; import { Provider } from 'react-redux' const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={ store }> <BrowserRouter> <App /> </BrowserRouter> </Provider> );
状态管理
入口页
// src/redux/store.js import { configureStore } from '@reduxjs/toolkit' import userReducer from './userSlice' const store = configureStore({ reducer: { user: userReducer } }) export default store
状态管理处理
// src/redux/userSlice.js // 这个不需要多个文件获取异步最新数据,只要请求一次放入userInfo就好 import { createSlice } from '@reduxjs/toolkit' const userSlice = createSlice({ name: 'userSlice', initialState:{ isLogin: false, userInfo: {} }, reducers: { initUserInfo(state, {payload}) { state.userInfo = payload }, changeLoginStatus(state, { payload }) { state.isLogin = payload } } }) export const { initUserInfo, changeLoginStatus } = userSlice.actions export default userSlice.reducer
模拟请求数据
// src/pages/Login.jsx // 请求成功返回对应的用户信息 import { useDispatch } from 'react-redux' import { getUserById } from '../api' // 封装的ajax请求 const dispatch = useDispatch() async function fetchData() { // 验证登录用户是否正确后,默认正确,传入用户id const result = await getUserById(data.userid) dispatch(initUserInfo(result.data)) dispatch(changeLoginStatus(true)) } // 封装的表单 <Button onClick={fetchData}>提交</Button>
获取状态仓库的数据
// src/pages/User.jsx import { useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' // 获取用户是否登录,usrInfo的获取方式是一样的,把 const { isLogin } = useSelector(state => state.user) const navigate = useNavigate() // 判断用户是否登录,未登录直接跳转到登录页 if (!isLogin) { navigate('/login') }
状态仓库带异步请求
方式1
在reducers里面处理
// src/redux/userSlice.js import { createAsyncThunk } from '@reduxjs/toolkit' import { getUserListApi } from '../api' // 封装的ajax请求 import { createSlice } from '@reduxjs/toolkit' // payload是传过来的参数, thunk是要做的处理 export const getUserList = createAsyncThunk('user/getUserList', async (payload, thunk) => { const {data} = await getUserListApi() thunk.dispatch(userInfo(data)) }) const userSlice = createSlice({ name: 'userSlice', initialState:{ userList: [] }, reducers: { userInfo(state, {payload}) { state.userList = payload } } }) export const { userInfo } = userSlice.actions export default userSlice.reducer
方式2
在extraReducers里面
// src/redux/userSlice.js import { createAsyncThunk } from '@reduxjs/toolkit' import { getUserListApi } from '../api' // 封装的ajax请求 import { createSlice } from '@reduxjs/toolkit' // payload是传过来的参数, thunk是要做的处理 export const getUserList = createAsyncThunk('user/getUserList', async (payload, thunk) => { const {data} = await getUserListApi() return data }) const userSlice = createSlice({ name: 'userSlice', initialState:{ userList: [] }, extraReducers: (builder) => { // 这里就会有三种状态,跟Promise一样,pedding、fulfilled... // builder.addCase是链式调用builder.addCase(...).addCase(...) builder.addCase(getUserList.fulfilled, (state, { payload }) => { state.userList = payload }) } }) export default userSlice.reducer
状态管理数据处理
方式1、2一样处理
// src/pages/User.jsx import { getUserList } from '../redux/userSlice' import { useSelector, useDispatch } from 'react-redux' import { useEffect } from 'react' import { addStuAsync, editStuAsync } from "../redux/apiStu"; ...关键代码 const { userList } = useSelector(state => state.user) const dispatch = useDispatch() useEffect(() => { async function fetchData() { if(userList.length === 0) { await dispatch(getUserList()) } } fetchData() }, [userList]) return( <div> userList.map(item => { return ( <div key={item.id}>{item.username}</div> ) }) </div> )
开发中跨域处理
http-proxy-middleware@3.0.0版本已证实有问题,安装后启动react项目浏览器加载不出页面,后台也不报错
推荐安装:http-proxy-middleware@2.0.6
// 两种方式 const { createProxyMiddleware } = require("http-proxy-middleware"); module.exports = function(app){ app.use( createProxyMiddleware( "/res", { target: "http://127.0.0.1:7001", changeOrigin : true, ws: true } ), createProxyMiddleware( "/api", { target: "http://127.0.0.1:7001", changeOrigin : true, ws: true } ), createProxyMiddleware( "/static", { target: "http://127.0.0.1:7001", changeOrigin : true, ws: true } ) ) /* app.use( createProxyMiddleware( "/api", { target: "http://127.0.0.1:7001", changeOrigin : true, ws: true } ) ) app.use( createProxyMiddleware( "/static", { target: "http://127.0.0.1:7001", changeOrigin : true, ws: true } ) ) */ }
引入Antd组件库
安装npm i antd -S
// src/index.js引入 import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { BrowserRouter } from 'react-router-dom' import './index.css' // 全局样式 import "antd/dist/antd.min.css"; import zhCN from "antd/es/locale/zh_CN"; // 中文语言包 import { ConfigProvider } from "antd"; import './css/App.css' import store from './redux/store'; // 本地仓库 import { Provider } from 'react-redux' // 使用它可以所有的子组件都可以拿到store const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <BrowserRouter> <ConfigProvider locale={zhCN}> <App /> </ConfigProvider> </BrowserRouter> </Provider> );