React高阶组件(HOC)

高阶组件的基本概念

  • 高阶组件(HOC,Higher-Order Components)不是组件,而是一个函数,它会接收一个组件作为参数并返回一个经过改造的新组件:

const EnhancedComponent = higherOrderComponent(WrappedComponent);
  • 需要区分的是,组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。

  • 高阶组件是 React 中用于复用组件逻辑的一种高级技巧。

使用高阶组件的原因

  • 在业务开发中,虽然不掌握高阶组件也可以完成项目的开发,但是如果我们能够灵活地使用高阶组件,可以让项目代码变得更加优雅,同时增强代码的复用性和灵活性,提升开发效率。

  • 同时,了解高阶组件对我们理解各种 React.js 第三方库的原理很有帮助。

  • 关于高阶组件能解决的问题可以简单概括成以下三个方面:

    • 抽取重复代码,实现组件复用,常见场景:页面复用。

    • 条件渲染,控制组件的渲染逻辑(渲染劫持),常见场景:权限控制。

    • 捕获/劫持被处理组件的生命周期,常见场景:组件渲染性能追踪、日志打点。

  • 可见,高阶组件的作用十分强大,接下来,我将对高阶组件的实现方式进行介绍,从而加深大家对高阶组件作用的理解。

高阶组件的实现

  • 通常情况下,实现高阶组件的方式有以下两种:

    • 返回一个无状态(stateless)的函数组件

    • 返回一个 class 组件

    • 属性代理(Props Proxy)

    • 反向继承(Inheritance Inversion)

  • 高阶组件实现方式的差异性决定了它们各自的应用场景:一个 React 组件包含了 propsstateref、生命周期方法、static方法和React 元素树几个重要部分,所以我将从以下几个方面对比两种高阶组件实现方式的差异性:

    • 原组件能否被包裹

    • 原组件是否被继承

    • 能否读取/操作原组件的 props

    • 能否读取/操作原组件的 state

    • 能否通过 ref 访问到原组件的 dom 元素

    • 是否影响原组件某些生命周期等方法

    • 是否取到原组件 static 方法

    • 能否劫持原组件生命周期方法

    • 能否渲染劫持

属性代理

  • 属性代理是最常见的实现方式,它本质上是使用组合的方式,通过将组件包装在容器组件中实现功能。

  • 属性代理方式实现的高阶组件和原组件的生命周期关系完全是React父子组件的生命周期关系,所以该方式实现的高阶组件会影响原组件某些生命周期等方法。

操作 props
  • 最简单的属性代理实现代码如下:

// 返回一个无状态的函数组件
function HOC(WrappedComponent) {const newProps = { type: 'HOC' };return props => <WrappedComponent {...props} {...newProps}/>;
}// 返回一个有状态的 class 组件
function HOC(WrappedComponent) {return class extends React.Component {render() {const newProps = { type: 'HOC' };return <WrappedComponent {...this.props} {...newProps}/>;}};
}
  • 从上面代码可以看到,通过属性代理方式实现的高阶组件包装后的组件可以拦截到父组件传递过来的 props,提前对 props 进行一些操作,比如增加一个 type 属性。

抽象 state
  • 需要注意的是,通过属性代理方式实现的高阶组件无法直接操作原组件的 state,但是可以通过 props 和回调函数对 state 进行抽象。️

  • 常见的例子是实现非受控组件到受控组件的转变:

// 高阶组件
function HOC(WrappedComponent) {return class extends React.Component {constructor(props) {super(props);this.state = {name: '',};this.onChange = this.onChange.bind(this);}onChange = (event) => {this.setState({name: event.target.value,})}render() {const newProps = {name: {value: this.state.name,onChange: this.onChange,},};return <WrappedComponent {...this.props} {...newProps} />;}};
}// 使用
@HOC
class Example extends Component {render() {return <input name="name" {...this.props.name} />;}
}
获取 refs 引用
  • 为了访问 DOM element (focus事件、动画、使用第三方 DOM 操作库),有时我们会用到组件的 ref 属性,关于refs 的介绍详见官方文档。

  • ref 属性只能声明在 class 类型的组件上,而无法声明在函数类型的组件上(因为无状态组件没有实例)。

  • 通过属性代理方式实现的高阶组件无法直接获取原组件的 refs 引用,但是可以通过在原组件的ref回调函数中调用父组件传入的 ref 回调函数来获取原组件的refs 引用。

  • 假设有一个 User 组件(原组件),它的代码如下:

import * as React from 'react';
import * as styles from './index.module.less';interface IProps {name: string;age: number;inputRef?: any;
}
class User extends React.Component<IProps> {private inputElement: any ;static sayHello () {console.error('hello world'); // tslint:disable-line}constructor (props: IProps) {super(props);this.focus = this.focus.bind(this);this.onChange = this.onChange.bind(this);}state = {name: '',age: 0,};componentDidMount () {this.setState({name: this.props.name,age: this.props.age,});}onChange = (e: any) => {this.setState({age: e.target.value,});}focus () {this.inputElement.focus();}render () {return (<div className={styles.wrapper}><div className={styles.nameWrapper}>姓名:{this.state.name}</div><div className={styles.ageWrapper}>年龄:<inputclassName={styles.input}value={this.state.age}onChange={this.onChange}type="number"ref={input => {if (this.props.inputRef) {this.props.inputRef(input); // 调用父组件传入的ref回调函数}this.inputElement = input;}}/></div><div><buttonclassName={styles.button}onClick={this.focus}>获取输入框焦点</button></div></div>);}
}export default User;
  • 通过属性代理方式实现的能获取原组件 refs 引用的高阶组件代码如下:

import * as React from 'react';
import * as styles from './index.module.less';function HOC (WrappedComponent: any) {let inputElement: any = null;function handleClick () {inputElement.focus();}function wrappedComponentStaic () {WrappedComponent.sayHello();}return (props: any) => (<div className={styles.hocWrapper}><WrappedComponentinputRef={(el: any) => { inputElement = el; }}{...props}/><inputtype="button"value="获取子组件输入框焦点"onClick={handleClick}className={styles.focusButton}/><inputtype="button"value="调用子组件static"onClick={wrappedComponentStaic}className={styles.callButton}/></div>);
}export default HOC;
  • 使用:

import React from 'react';
import HOC from '../../components/OperateRefsHOC';
import User from '../../components/User';const EnhanceUser = HOC(User);class OperateRefs extends React.Component<any> {render () {return <EnhanceUser name="小明" age={12} />;}
}export default OperateRefs;
  • 通过高阶组件包装以后的 EnhanceUser 组件可以可以访问到 User 组件中的 input 元素:

 

获取原组件的 static 方法
  • 当待处理组件为 class 组件时,通过属性代理实现的高阶组件(无论是返回一个函数组件 还是返回一个 class 组件,均)可以获取到原组件的 static 方法,如上面给出的高阶组件的代码,核心代码如下:

import * as React from 'react';
import * as styles from './index.module.less';function HOC (WrappedComponent: any) {/* 省略无关代码... */function wrappedComponentStaic () {WrappedComponent.sayHello();}return (props: any) => (<div className={styles.hocWrapper}><WrappedComponentinputRef={(el: any) => { inputElement = el; }}{...props}/>/* 省略无关代码... */<inputtype="button"value="调用子组件static"onClick={wrappedComponentStaic}className={styles.callButton}/></div>);
}export default HOC;
  • 效果如下:

 

通过 props 实现条件渲染
  • 通过属性代理方式实现的高阶组件无法直接实现对原组件进行渲染劫持(即对原组件内部 render 的控制并不是很强),但可以通过 props 来控制是否渲染及传入数据:

import * as React from 'react';
import * as styles from './index.module.less';function HOC (WrappedComponent: any) {/* 省略无关代码... */function wrappedComponentStaic () {WrappedComponent.sayHello();}return (props: any) => (<div className={styles.hocWrapper}>{props.isShow ? (<WrappedComponent{...props}/>) : <div>暂无数据</div>}</div>);
}export default HOC;
用其他元素包裹传入的组件
  • 我们可以通过类似下面的方式将原组件包裹起来,从而实现布局或者是样式的目的:

function withBackgroundColor(WrappedComponent) {return class extends React.Component {render() {return (<div style={{ backgroundColor: '#ccc' }}><WrappedComponent {...this.props} {...newProps} /></div>);}};
}

反向继承

  • 反向继承指的是使用一个函数接受一个组件作为参数传入,并返回一个继承了该传入组件的类组件,且在返回组件的 render() 方法中返回 super.render() 方法,最简单的实现如下:

const HOC = (WrappedComponent) => {return class extends WrappedComponent {render() {return super.render();}}
}
  • 相较于属性代理方式,使用反向继承方式实现的高阶组件的特点是允许高阶组件通过 this 访问到原组件,所以可以直接读取和操作原组件的 state/ref/生命周期方法。

  • 反向继承方式实现的高阶组件可以通过 super.render() 方法获取到传入组件实例的 render 结果,所以可对传入组件进行渲染劫持(最大特点),如:

    • 有条件地展示元素树(element tree

    • 操作由 render() 输出的 React 元素树

    • 在任何由 render() 输出的 React 元素中操作 props

    • 用其他元素包裹传入组件的渲染结果

劫持原组件生命周期方法
  • 因为反向继承方式实现的高阶组件返回的新组件是继承于传入组件,所以当新组件定义了同样的方法时,将会会覆盖父类(传入组件)的实例方法,如下面代码所示:

function HOC(WrappedComponent){// 继承了传入组件return class HOC extends WrappedComponent {// 注意:这里将重写 componentDidMount 方法componentDidMount(){...}render(){//使用 super 调用传入组件的 render 方法return super.render();}}
}
  • 虽然生命周期重写会被覆盖,但我们可以通过其他方式来劫持生命周期:

function HOC(WrappedComponent){const didMount = WrappedComponent.prototype.componentDidMount;// 继承了传入组件return class HOC extends WrappedComponent {componentDidMount(){// 劫持 WrappedComponent 组件的生命周期if (didMount) {didMount.apply(this);}...}render(){//使用 super 调用传入组件的 render 方法return super.render();}}
}
读取/操作原组件的 state
  • 反向继承方式实现的高阶组件中可以读取、编辑和删除传入组件实例中的 state,如下面代码所示:

function HOC(WrappedComponent){const didMount = WrappedComponent.prototype.componentDidMount;// 继承了传入组件return class HOC extends WrappedComponent {async componentDidMount(){if (didMount) {await didMount.apply(this);}// 将 state 中的 number 值修改成 2this.setState({ number: 2 });}render(){//使用 super 调用传入组件的 render 方法return super.render();}}
}
渲染劫持
条件渲染
  • 条件渲染指的是我们可以根据部分参数去决定是否渲染组件(与属性代理方式类似),如:

const HOC = (WrappedComponent) =>class extends WrappedComponent {render() {if (this.props.isRender) {return super.render();} else {return <div>暂无数据</div>;}}}
修改 React 元素树
  • 我们还可以通过 React.cloneElement 方法修改由 render 方法输出的 React 组件树:

// 例子来源于《深入React技术栈》
function HigherOrderComponent(WrappedComponent) {return class extends WrappedComponent {render() {const tree = super.render();const newProps = {};if (tree && tree.type === 'input') {newProps.value = 'something here';}const props = {...tree.props,...newProps,};const newTree = React.cloneElement(tree, props, tree.props.children);return newTree;}};
}

属性代理和反向继承的对比

  • 上面两个小节分别介绍了属性代理和反向继承两种方式实现的高阶组件:

    • 属性代理是从“组合”的角度出发,这样有利于从外部去操作 WrappedComponent,可以操作的对象是 props,或者在 WrappedComponent 外面加一些拦截器,控制器等。

    • 反向继承则是从“继承”的角度出发,是从内部去操作 WrappedComponent,也就是可以操作组件内部的 state ,生命周期,render函数等等。

  • 为了方便对比,对两种方式实现的高阶组件所具有的功能列表如下:

  • 可以看到,通过反向继承方法实现的高阶组件相较于属性代理实现的高阶组件,功能更强大,个性化程度更高,因此能适应更多的场景。

具体实践

  • 本文将介绍高阶组件在业务场景中的一些实践 。

页面复用

  • 前面提到,属性代理是最常见的高阶组件实现方式,它本质上是使用组合的方式,通过将组件包装在容器组件中实现组件逻辑复用的功能。 因此,如果想实现页面复用,可以使用属性代理方式实现的高阶组件。

  • 假设我们项目中有 pageA 和 pageB 两个 UI 交互完全相同的电影列表页,但由于属于不同的电影类别,数据来源及部分文案有所不同,普通写法可能是这样:

// views/PageA.js
import React from 'react';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';class PageA extends React.Component {state = {movieList: [],}/* ... */async componentDidMount() {const movieList = await fetchMovieListByType('comedy');this.setState({movieList,});}render() {return <MovieList data={this.state.movieList} emptyTips="暂无喜剧"/>}
}
export default PageA;// views/PageB.js
import React from 'react';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';class PageB extends React.Component {state = {movieList: [],}// ...async componentDidMount() {const movieList = await fetchMovieListByType('action');this.setState({movieList,});}render() {return <MovieList data={this.state.movieList} emptyTips="暂无动作片"/>}
}
export default PageB;
  • 通过观察发现,两个页面的代码有很多相同的代码,可能一开始觉得可以得过且过。但随着业务的进展,需要上线的越来越多类型的电影,每写一个新的页面就会新增一些重复的代码,这样明显是不合理的,所以我们需要对页面中的重复逻辑进行提取:

// HOC
import React from 'react';
const withFetchingHOC = (WrappedComponent, fetchingMethod, defaultProps) => {return class extends React.Component {async componentDidMount() {const data = await fetchingMethod();this.setState({data,});}render() {return (<WrappedComponent data={this.state.data} {...defaultProps} {...this.props} />);}}
}// 使用:
// views/PageA.js
import React from 'react';
import withFetchingHOC from '../hoc/withFetchingHOC';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';
const defaultProps = {emptyTips: '暂无喜剧'}export default withFetchingHOC(MovieList, fetchMovieListByType('comedy'), defaultProps);// views/PageB.js
import React from 'react';
import withFetchingHOC from '../hoc/withFetchingHOC';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';
const defaultProps = {emptyTips: '暂无动作片'}export default withFetchingHOC(MovieList, fetchMovieListByType('action'), defaultProps);;// views/PageOthers.js
import React from 'react';
import withFetchingHOC from '../hoc/withFetchingHOC';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';
const defaultProps = {...}export default withFetchingHOC(MovieList, fetchMovieListByType('some-other-type'), defaultProps);
  • 可以发现,上面设计的高阶组件 withFetchingHOC,把变的部分(组件和获取数据的方法) 抽离到外部作为传入,从而实现页面的复用。

 权限控制

 

  • 假设现在有这样一个场景:最近有一个新功能要上线,包含了一系列新开发的页面。现在需要对其中几个页面增加白名单功能,如果不在白名单中的用户访问这些页面只进行文案提示,不展示相关业务数据。一周(功能验收完成)后去掉白名单,对全部用户开放。

  • 以上场景中有几个条件:

    • 多个页面鉴权:鉴权代码不能重复写在页面组件中;

    • 不在白名单用户只进行文案提示:鉴权过程业务数据请求之前;

    • 一段时间后去掉白名单:鉴权应该完全与业务解耦,增加或去除鉴权应该最小化影响原有逻辑。

  • 思路:封装鉴权流程,利用高阶组件的条件渲染特性,鉴权失败展示相关文案,鉴权成功则渲染业务组件。由于属性代理和反向继承都可以实现条件渲染,下面我们将使用比较简单的属性代理方式实现的高阶组件来解决问题:

import React from 'react';
import { whiteListAuth } from '../lib/utils'; // 鉴权方法/*** 白名单权限校验* @param WrappedComponent* @returns {AuthWrappedComponent}* @constructor*/
function AuthWrapper(WrappedComponent) {return class AuthWrappedComponent extends React.Component {constructor(props) {super(props);this.state = {permissionDenied: -1,};}async componentDidMount() {try {await whiteListAuth(); // 请求鉴权接口this.setState({permissionDenied: 0,});} catch (err) {this.setState({permissionDenied: 1,});}}render() {if (this.state.permissionDenied === -1) {return null; // 鉴权接口请求未完成}if (this.state.permissionDenied) {return <div>功能即将上线,敬请期待~</div>;}return <WrappedComponent {...this.props} />;}}
}export default AuthWrapper;
  • 对于需要加权限控制的页面,只需要将页面组件作为参数传给高阶组件 AuthWrapper 即可。

  • 通过使用高阶组件,使得鉴权与业务完全解耦,也避免了鉴权失败时多余的业务数据请求,只需要增加/删除少量代码,即可增加/去除用户白名单的控制,原有业务组件的逻辑也不会受到影响。

组件渲染性能追踪 

  • 前面介绍的两个例子都是使用属性代理的方式实现高阶组件,本小节介绍的,则是使用反向继承方式实现的高阶组件完成组件渲染性能的追踪。

  • 前面提到 ,反向继承方式实现的高阶组件能否劫持原组件生命周期方法,因此,利用该特性,我们可以方便的对某个组件的渲染时间进行记录:

import React from 'react';
// Home 组件
class Home extends React.Component {render () {return (<h1>Hello World.</h1>);}
}// HOC
function withTiming (WrappedComponent: any) {let start: number, end: number;return class extends WrappedComponent {constructor (props: any) {super(props);start = 0;end = 0;}componentWillMount () {if (super.componentWillMount) {super.componentWillMount();}start = +Date.now();}componentDidMount () {if (super.componentDidMount) {super.componentDidMount();}end = +Date.now();console.error(`${WrappedComponent.name} 组件渲染时间为 ${end - start} ms`);}render () {return super.render();}};
}export default withTiming(Home);

 

Hook 会替代高阶组件吗? 

  • Hook 是 React 16.8 的新增特性,它可以让我们在不编写 class 的情况下使用 state 以及其他的 React 特性(关于 Hook 的相关介绍可阅读官方文档)。

  • Hook 的出现使得原本许多很别扭的写法变得轻松,最典型的就是它可以取代掉 class 生命周期中大多数的功能,把更相关的逻辑放在一起,而非零散在各个生命周期实例方法中。

  • 虽然 Hook 能解决许多难题,但这显然并不意味着 Hook 就能取代高阶组件,因为它们其实还是有着各自的优势所在:

    • 高阶组件可以做到很轻松地外部协议化注入功能到一个基础 Component 中,所以可以用来做插件,如 react-swipeable-views中的 autoPlay 高阶组件,通过注入状态化的 props 的方式对组件进行功能扩展,而不是直接将代码写在主库中。对于 Hook 来说,其中间处理过程一定会与目标组件强依赖(不是 Hook 的缺陷,只是 Hook 显然并不是设计来解决插件注入的问题的)。

    • Hook 更多可以看作是对高阶组件方案的补充,填补了高阶组件不擅长的部分。Hook 的写法可以让代码更加紧凑,更适合做 Controller 或者需要内聚的相关逻辑。

    • 目前 Hook 还处于早期阶段(React 16.8.0 才正式发布Hook 稳定版本),一些第三方的库可能还暂时无法兼容 Hook

  • React 官方还没有把 class 从 React 中移除的打算,class 组件和 Hook 完全可以同时存在。官方也建议避免任何“大范围重构”,毕竟 Hook 是一个非常新的特性,如果你喜欢它,可以在新的非关键性的代码中使用Hook

总结 

  • 高阶组件不是组件,它是一个将某个组件转换成另一个组件的纯函数。

  • 高阶组件的主要作用是实现代码复用和逻辑抽象、对 state 和 props 进行抽象和操作、对组件进行细化(如添加生命周期)、实现渲染劫持等。在实际的业务场景中合理的使用高阶组件,可以提高开发效率和提升代码的可维护性。

  • 高阶组件的实用性使其频繁地被大量 React.js 相关的第三方库,如 React-Redux的 connect 方法、React-Loadable等所使用,了解高阶组件对我们理解各种 React.js 第三方库的原理很有帮助。

  • 高阶组件有两种实现方式,分别是属性代理和反向继承。它可以看作是装饰器模式在 React 中的实现:在不修改原组件的情况下实现组件功能的增强。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/285000.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

腾讯云GPU云服务器_GPU云计算_异构计算_弹性计算

腾讯云GPU服务器是提供GPU算力的弹性计算服务&#xff0c;腾讯云GPU服务器具有超强的并行计算能力&#xff0c;可用于深度学习训练、科学计算、图形图像处理、视频编解码等场景&#xff0c;腾讯云百科txybk.com整理腾讯云GPU服务器租用价格表、GPU实例优势、GPU解决方案、GPU软…

HarmonyOS实战开发-如何使用首选项能力实现一个简单示例。

介绍 本篇Codelab是基于HarmonyOS的首选项能力实现的一个简单示例。实现如下功能&#xff1a; 创建首选项数据文件。将用户输入的水果名称和数量&#xff0c;写入到首选项数据库。读取首选项数据库中的数据。删除首选项数据文件。 最终效果图如下&#xff1a; 相关概念 首选…

学习vue3第十节(插槽v-slot)

本节主要介绍一下 v-slot 插槽指令&#xff0c;以及插槽相关内容 1、定义&#xff1a; 子组件给父组件提供使用的一个位置&#xff0c;使用<slot></slot>表示&#xff0c;父组件可以在这个位置填充任何代码&#xff1b; 2、默认插槽 匿名插槽&#xff1a;会自定…

简单使用Swagger

文章目录 1、介绍2、 使用步骤3、 常用注解 1、介绍 Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是&#xff1a; 使得前后端分离开发更加方便&#xff0c;有利于团队协作 接口的文…

基于SSM非遗视域下喀什旅游网站

ssm非遗视域下喀什旅游网站的设计与实现 摘要 我们的生活水平正在不断的提高&#xff0c;然而提高的一个重要的侧面表现就是更加注重我们的娱乐生活。旅行是我们都喜欢的一种娱乐方式&#xff0c;各式各样的旅行经历给我们带来的喜悦也是大不相同的。带来快乐的同时也因为其复…

微信小程序实现多张照片上传

hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#xff1a;code袁 &#x1f4a5; 所属专栏&…

第一篇:概述、 目录、适用范围及术语 --- IAB/MRC《增强现实(AR)广告(效果)测量指南1.0 》

第一篇&#xff1a;概述、目录、适用范围及术语 - IAB与MRC及《增强现实广告效果测量指南1.0》 --- 我为什么要翻译美国IAB科技公司系列标准 ​​​​​​​​​​​​​​ 翻译计划 第一篇概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇广告效…

6.3 BP神经网络

在多层感知器被引入的同时&#xff0c;也引入了一个新的问题&#xff1a;由于隐藏层的预期输出并没有在训练样例中给出&#xff0c;隐藏层结点的误差无法像单层感知器那样直接计算得到。 为了解决这个问题&#xff0c;反向传播&#xff08;BP&#xff09;算法被引入&#xff0…

推荐一种Bean注入方式——开发经验

我们都知道三种Bean注入的方式分别是属性注入&#xff0c;setter方法注入&#xff0c;构造器注入。这三种Bean注入的方式各有优缺点&#xff0c;但是相对来说更推荐使用构造器注入的方式。 1、构造器注入的优缺点 优点&#xff1a; 1、可以注入不可变对象 因为构造方法注入是…

Redis技术学习|实战项目记录|商户缓存

学习资料声明 黑马程序员的Redis学习视频&#xff1a;黑马程序员Redis入门到实战教程 需要用到的知识&#xff1a;linux&#xff08;推荐韩顺平老师的教程&#xff0c;学到p30&#xff0c;创建好虚拟机和简单的几个命令就好。&#xff09;SSM。SpringBoot。 还用到了MybatisPl…

mac 解决随机出现的蓝色框

macbookair为什么打字的时候按空格键会出现蓝色框? - 知乎

c#矩阵求逆

目录 一、矩阵求逆的数学方法 1、伴随矩阵法 2、初等变换法 3、分块矩阵法 4、定义法 二、矩阵求逆C#代码 1、伴随矩阵法求指定3*3阶数矩阵的逆矩阵 &#xff08;1&#xff09;伴随矩阵数学方法 &#xff08;2&#xff09;代码 &#xff08;3&#xff09;计算 2、对…

橘子疾病检测4种YOLOV8

橘子检测YOLOV8&#xff0c;检测4种疾病&#xff0c;采用YOLOV8-NANO&#xff0c;训练得到PT模型转换成ONNX&#xff0c;最后OPENCV调用&#xff0c;支持C/PYTHON/ANDROID 橘子检测YOLOV8&#xff0c;检测4种疾病

阿里云4核16G服务器优惠价格26元1个月、149元半年

阿里云4核16G服务器优惠价格26.52元1个月、79.56元3个月、149.00元半年。2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也…

Ubuntu18.04桌面版设置静态IP地址

引用: Ubuntu配置静态IP_ubuntu配置静态ip地址-CSDN博客 正文 默认Unbuntu 18.04 Desktop桌面版使用 netplan 管理网卡网络地址。使用Unbuntu 18.04 桌面版配置&#xff0c;可以通过桌面上的设置图标配置网卡的静态IP地址。 点击桌面右上角下拉框&#xff0c;点击“设置”按…

206.反转链表

刷算法题&#xff1a; 第一遍&#xff1a;1.看5分钟&#xff0c;没思路看题解 2.通过题解改进自己的解法&#xff0c;并且要写每行的注释以及自己的思路。 3.思考自己做到了题解的哪一步&#xff0c;下次怎么才能做对(总结方法) 4.整理到自己的自媒体平台。 5.再刷重复的类…

yolov5训练并生成rknn模型部署在RK3588开发板上,实现NPU加速推理

简介 RK3588是瑞芯微&#xff08;Rockchip&#xff09;公司推出的一款高性能、低功耗的集成电路芯片。它采用了先进的28纳米工艺技术&#xff0c;并配备了八核心的ARM Cortex-A76和Cortex-A55处理器&#xff0c;以及ARM Mali-G76 GPU。该芯片支持多种接口和功能&#xff0c;适…

Python Flask 将数据传递给前端

from flask import Flask, render_templateapp Flask(__name__)app.route("/index") def index():data {name: "张三","age": 18,}return render_template("index2.html", datadata)if __name__ __main__:app.run()<!DOCTYPE ht…

【python 装饰器 - 重试】做一个简易重试装饰器,如果函数执行错误则会自动重新执行,可设置重试次数,对爬虫比较友好

文章日期&#xff1a;2024.03.19 使用工具&#xff1a;Python 类型&#xff1a;装饰器 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标准算法&#xff09;&…

js教程(8)

一、事件流 1.概述 在JavaScript中&#xff0c;事件流描述的是事件在DOM结构中传播和被处理的顺序。事件流分为冒泡阶段和捕获阶段。 冒泡阶段&#xff08;Bubbling Phase&#xff09;&#xff1a;事件首先从最内层的元素开始向父级元素传播&#xff0c;一直传播到最外层的元素…