当前位置: 首页 > news >正文

React基础知识一

写的东西太多了,照成csdn文档编辑器都开始卡顿了,所以分篇写。

1.安装React

需要安装下面三个包。

react:react核心包
react-dom:渲染需要用到的核心包
babel:将jsx语法转换成React代码的工具。(没使用jsx可以不装)

1.1 在html中引入CND

在html中使用react是一种简便的方式。非常有利于初学者学习。
地址为:https://zh-hans.react.dev/learn/installation
需要引入下面这三个CDN
注意:这三个顺序是不能变的

    <script src="https://unpkg.com/react@18/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script><!-- Don't use this in production: --><script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

1.2 React初体验

1.2.1 JSX写法(掌握)

在html里面添加下面的代码。注意一定要添加type="text/babel"不然是会不识别语法的。

<body><div id="root"></div><script type="text/babel">//1.创建虚拟DOMconst VDOM=<h1>Hello React</h1>//或const VDOM = (//不加小括号换行也是不会报错的,仅仅是为了结构好管理<h1><span>Hello React</span></h1>)//2.渲染虚拟DOM到页面ReactDOM.render(VDOM,document.getElementById('root'));</script>
</body>
1.2.2 React原始写法(了解)

下面这种写法可以不使用babel,也就不用引用babel包,所以类型也可以写成type=“text/javascript”。
这是react提供的语法糖,react自己的原始代码都是这种形式写的。对于开发者来说,这种语法是非常难用的。JSX就是react为我们提供的方便使用的语法。
下面的写法不用手动写,开发就用JSX,但是React会把JSX转化成下面的写法,了解一下还是很有必要的。

<body><div id="root"></div><div id="root2"></div><script type="text/javascript">//1.创建虚拟DOMconst VDOM = React.createElement('h1',{id:"title"},"Hello React")//嵌套多层的时候非常的麻烦const VDOM2 =  React.createElement('h1',{id:"title"},React.createElement('span',{},"Hello React"))//2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('root'));ReactDOM.render(VDOM2, document.getElementById('root2'));</script>
</body>
1.2.3组件写法(后面掌握)

这种写法可以先跳过,后面学组件的时候再说。

    <div id="root"></div><script type="text/babel">function MyApp() {//这可以理解为一个组件return <h1>Hello, world!</h1>;//不要奇怪,这是jsx语法,就是支持这样写的}const container = document.getElementById('root');const root = ReactDOM.createRoot(container);//18以前这个函数名字叫renderroot.render(<MyApp />);</script>

运行一下,就有hello world了。
在这里插入图片描述
上面的代码可以这样写,实现一样的效果。

      const container = document.getElementById('root');const root = ReactDOM.createRoot(container);//18以前这个函数名字叫renderroot.render(<h1>Hello, world!</h1>);

1.3 真实DOM和虚拟DOM的区别

我们把虚拟DOM直接输出,可以看到虚拟DOM就是一个普通的js对象。

        const VDOM = React.createElement('h1', { id: "title" }, "Hello React")console.log("虚拟DOM:",VDOM);

在这里插入图片描述
真实DOM可以通过下面的方式输出。

        const realDOM=document.getElementById("root")console.log("真实DOM:",realDOM);

可以看到在浏览器直接把内容给输出了。
在这里插入图片描述
这时候我们需要debugger断点来看看他的真实内容是怎么样的。
可以看到,真实DOM是有非常多的属性的,而虚拟DOM的属性实际上非常的少。
在这里插入图片描述
或者使用console.dir也是可以直接输出的。g

console.dir(realDOM);

小结:
1.虚拟DOM本质上是一个普通对象。
2.虚拟DOM比较轻,真实DOM比较重,因为react不需要那么多属性。
3.虚拟DOM最终被react转化为真实DOM,呈现在页面上。

2.JSX语法规则

1.定义虚拟DOM时,不要写引号

const VDOM=<h1>Hello React</h1>

2.标签中引入JS表达式需要用{}
不需要加引号,直接跟{}

    const myId="title"const myData="Hello World"const VDOM = (<h1 id={myId}><span>{myData}</span></h1>)

3.不允许写class这个属性,而要写className
这是因为class是一个ES6类的关键词,React为了避开这个,自己定义了className。

.title{background:orange
}
//直接跟类名
<h1 className="title">Hello React</h1>

4.style不能写成字符串的形式,而是要写成对象的形式
需要大括号包裹。注意下面的{{}}并不是类似vue的mustache语法,而是和变量外面要使用{}是一样的道理,里面嵌套的{}表示的是这是一个对象。

<h1 style={{color:'lightblue',fontSize='29px'}}>Hello React</h1>

5.虚拟DOM只能有一个根元素
6.标签必须闭合

<input type="text" />
或者
<input type="text"><input/>

7.标签首字母
(1).若小写字母开头,则将该标签转为html同名元素,若html中无该标签对应的同名元素,则报错
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
注意: 你写的这些div标签都是JSX标签,不是html标签,或者说JSX给你提供了类似html的语法,最终他会转变成html标签在浏览器展示。

3.循环列表

注意,循环只能使用map这种表达式,不可以用for循环,因为for循环是语句。
{}里面是只能写js表达式,不能写js语句,所以for和if判断都是不可以写的。

这里必须需要理解清楚表达式语句的区别
表达式: 表达式会产生一个值,这个值可以放在任意一个需要值的地方。

a
a+b
foo(1)
arr.map()
console.log("name")
function test(){}//方法也是表达式,也是有返回值的

语句(代码):

if(){}
for(){}
switch(){}
<body><div id="root"></div><script type="text/babel">const data = ['Angular', 'React', 'Vue']const VDOM = (<div><h1>遍历列表</h1><ul>{data.map((item,index)=>{//这种方式实现key是不太合适的,后面再说return <li key={index}>{item}</li>})}</ul></div>)//2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('root'));</script>
</body>

4.组件编程

4.1 react浏览器插件安装

推荐使用edge浏览器。下载方便,注意需要把下面两个勾打上,不然本地路径图标是不会亮的。
在这里插入图片描述
f12刷新一下开发者工具可以看到Components和Profiler这两个功能模块。
在这里插入图片描述

4.2 函数式组件

需要注意的是函数名字需要大写,不然报错。render函数的第一个参数需要写成标签的形式,不然报错。

        <div id="root"></div><script type="text/babel">function MyComponent(){return <div>我是一个函数式组件(适用于简单组件的定义)</div>}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

在这里插入图片描述

4.2.1 函数式组件this的指向

this指向是undefined,因为react在被babel翻译的时候开启了严格模式。

4.3 类式组件

        <div id="root"></div><script type="text/babel">class MyComponent extends React.Component{render(){return <div>这是一个类式组件(用于复杂的场景)</div>}}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

1.react解析MyComponent标签,找到MyComponent组件。
2.发现组件是类定义的,new出该类的实例,通过实例调用render方法。
3.将render方法返回的虚拟DOM转化为真实DOM,在浏览器上呈现。

render里面的this是组件实例对象。

5.组件实例三大属性之state

简单使用

props必写,不写报错。继承必须调用构造器,这是class规定的。直接通过this.state赋值就可以了。

        <div id="root"></div><script type="text/babel">class MyComponent extends React.Component{constructor(props){//props必写,不写报错super(props)//继承必须调用构造器,这是class规定的this.state={isHot:false}}render(){return <div>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>}}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

回调事件

通过onClick指定点击函数。

        <div id="root"></div><script type="text/babel">class MyComponent extends React.Component{constructor(props){//props必写,不写报错super(props)//继承必须调用构造器,这是class规定的this.state={isHot:false}}render(){return <div onClick={changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>}}function changeWeather(){console.log("被点击了");}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

修改state的值

点击事件虽然有了,但是如果我们想要通过下面的代码来修改state的值的话是会报错的。因为this是undefined。

             function changeWeather(){console.log("被点击了");this.state.isHot=false}

我们可以定义一个that变量,在构造器里面把this赋值给that,这样就可以在外面获取到组件实例对象了。

        <div id="root"></div><script type="text/babel">let thatclass MyComponent extends React.Component{constructor(props){//props必写,不写报错super(props)//继承必须调用构造器,这是class规定的this.state={isHot:true}that=this}render(){return <div onClick={changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>}}function changeWeather(){console.log("被点击了",that);// that.state.isHot=false}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

这种方式虽然可行,但是非常的不合理。因为写的代码都是零散的,没有组件的封装性。
试着把代码改成下面这个样子。这里存在非常多的问题。
1.onClick={this.changeWeather},需要写this.changeWeather而不是直接写changeWeather。这是因为现在changeWeather是写在类里面,而写类外面是直接调用。
2.render和constructor的this是组件的实例对象,而changeWeather的this却是undefined。

        <div id="root"></div><script type="text/babel">class MyComponent extends React.Component{constructor(props){//props必写,不写报错super(props)//继承必须调用构造器,这是class规定的this.state={isHot:true}}render(){return <div onClick={this.changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>}changeWeather(){console.log("被点击了",this);// that.state.isHot=false}}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

正确的做法: (没搞懂)
添加this.changeWeather=this.changeWeather.bind(this)这一行就可以了。

        <div id="root"></div><script type="text/babel">class MyComponent extends React.Component{constructor(props){//props必写,不写报错super(props)//继承必须调用构造器,这是class规定的this.state={isHot:true}this.changeWeather=this.changeWeather.bind(this)}render(){return <div onClick={this.changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>}changeWeather(){console.log("被点击了",this);// that.state.isHot=false}}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

也可以写成下面这个样子:(没搞懂)

        <div id="root"></div><script type="text/babel">class MyComponent extends React.Component{constructor(props){//props必写,不写报错super(props)//继承必须调用构造器,这是class规定的this.state={isHot:true}this.demo=this.changeWeather.bind(this)}render(){return <div onClick={this.demo}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>}changeWeather(){console.log("被点击了",this);// that.state.isHot=false}}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

通过setState修改值

直接修改值是没有响应式的,值虽然修改成功了,但没有响应式。

        <div id="root"></div><script type="text/babel">class MyComponent extends React.Component{constructor(props){//props必写,不写报错super(props)//继承必须调用构造器,这是class规定的this.state={isHot:true}this.demo=this.changeWeather.bind(this)console.log(this);}render(){return <div onClick={this.demo}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>}changeWeather(){console.log("被点击了",this);// this.state.isHot=!this.state.isHotthis.setState({isHot:!this.state.isHot})}}//2.渲染虚拟DOM到页面ReactDOM.render(<MyComponent/>, document.getElementById('root'));</script>

setState这个方法是来自React.Component这个类的,可以通过原型链查看到。
在这里插入图片描述
setState会合并对象而不是替换对象。也就是可以指定属性进行修改。

state的精简写法(最正确的写法 )

1.首先,利用class的特性,写在类里面的属性会直接挂载到类实例对象上。构造器可以不用写,直接定义一个state属性就可以。
2.每定义一个点击回调函数都要在构造器里面写类似this.changeWeather=this.changeWeather.bind(this)这样的代码,非常的麻烦。可以利用箭头函数,会自动向上查找this的特性完美的处理this指向问题。

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }render() {return <div onClick={this.changeWeather}>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</div>}changeWeather = () => {this.setState({ isHot: !this.state.isHot })}}ReactDOM.render(<MyComponent />, document.getElementById('root'));</script>

小结

state是三大属性中最难最重要的。掌握了state,最难的骨头已经啃下来了。

6.组件实例三大属性之props

基本使用

非常简单,没什么问题。

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }render() {return (<div><ul><li>姓名:{this.props.name}</li><li>年龄:{this.props.age}</li></ul></div>)}changeWeather = () => {this.setState({ isHot: !this.state.isHot })}}ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));</script>

批量传递props

写成下面这样就可以用对象来批量传递属性了。

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }render() {return (<div><ul><li>姓名:{this.props.name}</li><li>年龄:{this.props.age}</li></ul></div>)}changeWeather = () => {this.setState({ isHot: !this.state.isHot })}}const p={name:"Tom",age:18}ReactDOM.render(<MyComponent {...p} />, document.getElementById('root'));</script>

细节:
这里有个细节需要讲一下。{…p}并不是展开对象。
在纯js里面,下面这样的写法是错误的,对象是不能通过…展开的,数组可以,需要是可迭代对象。

        const p={name:"Tom",age:18}console.log(...p);

在纯js里面,下面的写法是正确的,这是es6创建对象的一种方式。相当于复制对象。

        const p={name:"Tom",age:18}const p2={...p}console.log(p2);

但如果是在react标签里面,{}是react的表达式符号,不是对象,…p是不能展开对象的,按照js的语法的话,但在react里面可以,这是babel和react帮我们实现的。

<MyComponent {...p} />

在react里面写下面的代码,不会报错,但也没内容,这是react做了处理的结果。

        const p={name:"Tom",age:18}console.log(...p);

props类型限制

在react15之前是不需要额外引用包的。
是直接写在react包里面的,后面抽离了出去。基本没人用React15了。也不会有这种写法了。

MyComponent.propTypes = {name: React.PropTypes.string}

新写法:react16以后
需要引入这个包。

<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>

类型定义和默认值,没什么好说的。

        MyComponent.propTypes = {name: PropTypes.string.isRequired,age: PropTypes.number,speak: PropTypes.func}MyComponent.defaultProps = {name: "Tom",age: 19}

props的精简写法

把原本写在外面的写到类里面,并加上static就行了。
加static相当于直接挂载到类本身上,而不是实例对象上。

        class MyComponent extends React.Component {state = { isHot: true }static propTypes = {name: PropTypes.string.isRequired,age: PropTypes.number,speak: PropTypes.func}static defaultProps = {name: "Tom",age: 19}render() {return (<div><ul><li>姓名:{this.props.name}</li><li>年龄:{this.props.age}</li></ul></div>)}changeWeather = () => {this.setState({ isHot: !this.state.isHot })}}

构造器和props

构造器可以不写,但是写了必须写props和super,不然会出现在构造器里面调用this.props出现undefined的bug。这个官方文档里面明确写的。

            constructor(props){super(props)console.log(this.props);//不写和不传super,this.props是undefiend}

这种情况是非常少见的,几乎不会出现。一是你不用写构造器也可以在别的地方使用this.props,二是如果你写了构造器,那么参数的props是可以直接使用的,不需要使用this.props。

函数式组件中使用props

在函数式组件里面,我们只能玩玩props,state和refs是玩不了的,因为没有this。而且props之所以能玩也是因为函数能传参数这个特性。类型限制只能写在外面。

    <div id="root"></div><script type="text/babel">function Person(props) {return (<div><ul><li>姓名:{props.name}</li><li>年龄:{props.age}</li></ul></div>)}//类型限制只能写在外面Person.propTypes = {name: PropTypes.string.isRequired,age: PropTypes.number,speak: PropTypes.func}Person.defaultProps = {name: "Tom",age: 19}const p = { name: "Tom", age: 18 }ReactDOM.render(<Person name="Jack" age="20" />, document.getElementById('root'));</script>

7.组件实例三大属性之refs

7.1 基本使用(字符串类型的ref)

通过下面的代码使用基本功能:
1.点击按钮显示输入框内容。
2.输入框焦点离开,显示输入框内容。

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }render() {return (<div><input id="input1" placeholder="请输入内容" />&nbsp;<button onClick={this.showData}>点击显示内容</button>&nbsp;<input id="input2" placeholder="" onBlur={this.showData2} />&nbsp;</div>)}showData = () => {const input=document.getElementById("input1")alert(input.value)}showData2 = () => {const input=document.getElementById("input2")alert(input.value)}}ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));</script>

上面代码是通过原生getElementById实现的,不太合适。我们希望通过react实现。react的refs就可以实现类似的功能。

react的refs是用来给元素打标识用的。

修改后的代码如下,非常的简单。

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }render() {return (<div><input ref='input1' id="input1" placeholder="请输入内容" />&nbsp;<button onClick={this.showData}>点击显示内容</button>&nbsp;<input ref='input2' id="input2" placeholder="" onBlur={this.showData2} />&nbsp;</div>)}showData = () => {// const input=document.getElementById("input1")const {input1}=this.refsalert(input1.value)}showData2 = () => {// const input=document.getElementById("input2")const {input2}=this.refsalert(input2.value)}}ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));</script>

在这里插入图片描述
注意:这里获取到的refs的内容是真实DOM节点的内容。
我们输出this看下refs长什么样子。是一个键值对,key是ref的名字,value是元素的类型加元素的id。
没有写id的话,我都不知道后面会跟id。
在这里插入图片描述

7.2 回调形式的ref

字符串形式的ref是最简单的一种ref。但是官方不推荐使用了,因为存在效率问题。不过如果你用的是16.8版本的话,继续用也没什么大问题,毕竟用起来简单。

下面是回调函数的用法,个人觉得不好用,写起来冗余和麻烦。

    <script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }render() {console.log(this);return (<div><input ref={e=> this.input1 = e} id="input1" placeholder="请输入内容" />&nbsp;<button onClick={this.showData}>点击显示内容</button>&nbsp;<input ref={e=> this.input2 = e} id="input2" placeholder="" onBlur={this.showData2} />&nbsp;</div>)}showData = () => {const { input1 } = thisalert(input1.value)}showData2 = () => {const { input2 } = thisalert(input2.value)}}ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));</script>

7.3 回调形式ref的调用次数问题

这个在官方文档里面有提到,在写内联函数的情况下,第一次渲染的时候会调用一次,当页面刷新的时候,会调用两次,第一次内联函数的参数值是null(被重置了),第二次是正常值。

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }render() {return (<div><span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span><input ref={e => { this.input1 = e; console.log("@:", e); }} id="input1" placeholder="请输入内容" />&nbsp;<button onClick={this.showData}>点击显示内容</button>&nbsp;</div>)}showData = () => {this.setState({isHot: !this.state.isHot})}}ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));</script>

在这里插入图片描述
写成绑定函数的形式就不会出现这种问题了,但这个问题是无关紧要的。下面的这种写法非常的麻烦,完全没有必要。 用内联回调函数的形式是完全没有问题的。

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }render() {return (<div><span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span><input ref={this.saveInput} id="input1" placeholder="请输入内容" />&nbsp;<button onClick={this.showData}>点击显示内容</button>&nbsp;</div>)}showData = () => {this.setState({isHot: !this.state.isHot})}saveInput = (e)=>{this.input1=econsole.log("@:",e);}}ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));</script>

7.4 通过createRef创建ref (推荐)

这种方式是官方推荐的,用起来不是很麻烦,也解决了性能问题。不过最简单还是字符串的形式。不过发生性能问题就不好说了。字符串形式还是不要使用比较好。

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = { isHot: true }myRef1=React.createRef()myRef2=React.createRef()render() {return (<div><span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span><input ref={this.myRef1} id="input1" placeholder="请输入内容" />&nbsp;<button onClick={this.showData}>点击显示内容</button>&nbsp;<input ref={this.myRef2} onBlur={this.showData2} id="input1" placeholder="请输入内容" />&nbsp;</div>)}showData = () => {alert(this.myRef1.current.value)}showData2 = () => {alert(this.myRef2.current.value)}}ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));</script>

react脚手架使用和搭建

后面的有些内容不用脚手架建立工程项目的话使用起来不是很方便。或者不知道怎么使用,也不想花费大量的时间研究可能用不上的使用方式。所以用工程项目还是最有性价比的选择。

手动搭建react项目

通过下面的命令创建一个脚手架项目:

npm install -g create-react-app
create-react-app react-demos

默认目录结构如下:
在这里插入图片描述
这些内容可以作为参考,但是现在我们把src下的内容全部删掉,因为我们想要重头搭建我们的项目。
在这里插入图片描述
这时候我们再运行会告诉我们没有index.js。这是因为webpack需要index.js作为程序的入口。
在这里插入图片描述
非常神奇的是,你只要添加一个空白index.js文件,项目就可以运行的,只不过页面是空白的。
我们往index.js添加下面的内容,其中#root是index.html中的根元素,webpack自己会取找。

import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<h1>Hello React</h1>);

跑起来我们就可以看到下面的内容。
在这里插入图片描述
我们可以定义一个App类组件来封装我们的内容。

import ReactDOM from "react-dom/client";
import React from "react";
class App extends React.Component {render() {return <h1>Hello React</h1>;}
}
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

也可以把App组件单独写到外面。

import React from "react";
class App extends React.Component {render() {return <h1>Hello React</h1>;}
}
export default App;
import ReactDOM from "react-dom/client";
import React from "react";
import App from "./App";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

我们可以定义自己的组件,然后在App.jsx里面引入

import React from "react";
class HelloReact extends React.Component {render() {return <h1>Hello React</h1>;}
}
export default HelloReact;

这样,我们的工程就搭建完成了。

import React from "react";
import HelloReact from "./HelloReact";
class App extends React.Component {render() {return (<div><HelloReact /></div>);}
}
export default App;

通过eject命令查看webpack配置

我们可以看到react脚手架创建的项目实际上是通过react-scripts这个工具来运行的。而webpack的配置就在这个包里面的。

  "scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test","eject": "react-scripts eject"},

可以看到webpack的配置在这个包下面。
在这里插入图片描述
react-scripts这个工具提供了eject命令来暴露webpack的配置,但是一般不推荐这么做。一是因为webpack的配置非常复杂且难懂,二是一般我们专注开发,不要花太多精力在这些东西上。

一定要查看和修改也是有办法的,执行下面的命令:

npm run eject

会弹出警告,问你要不要弹出配置文件,这个操作是不可逆的。
在这里插入图片描述
执行完后,会多出config和scripts两个文件夹。
在这里插入图片描述
scripts节点的内容也变成下面的内容。

  "scripts": {"start": "node scripts/start.js","build": "node scripts/build.js","test": "node scripts/test.js"},

整个package.json都变了。依赖什么都一股脑的写道里面,非常的乱。所以还是不要弹出配置比较好。

使用craco来管理配置

这是一个第三方工具。主流的工具,大家都在用。

组件的生命周期

生命周期是非常重要的内容,我们需要在正确的生命周期做正确的事情。
这么这个网站是react官方声明周期图的地址,16.8后,react在官方文档里面已经移除了到这个图的导航。
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

这个图有两个版本,一个简单版,一个显示不常用生命周期版本。如果只看简单版的话,react的生命周期还是比较简单的。
在这里插入图片描述
不常用生命周期:
在这里插入图片描述

组件挂载和componentDidMount方法

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {constructor(props) {super(props);console.log("constructor");}render() {console.log("render");}componentDidMount() {console.log("compoentDidMount");}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

组件先执行constructor,再执行render,再执行componentDidMount。
在这里插入图片描述

父子组件的componentDidMount方法

如果是父子组件的话,情况就复杂一点。

      class MyComponent extends React.Component {constructor(props) {super(props);console.log("constructor");}render() {console.log("render");return <SubComponent />;}componentDidMount() {console.log("compoentDidMount");}}class SubComponent extends React.Component {constructor(props) {super(props);console.log("sub constructor");}render() {console.log("sub render");}componentDidMount() {console.log("sub compoentDidMount");}}ReactDOM.render(<MyComponent />, document.getElementById("root"));

父组件的componentDidMount是最后执行的。但父组件是先被挂载的。之所以会出现父组件的componentDidMount后执行是因为使用的是深度优先遍历算法。
在这里插入图片描述

更新数据时的生命周期和componentDidUpdate方法

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {message: "this is message",};constructor(props) {super(props);console.log("constructor");}changeMessage = () => {this.setState({message: "message changed",});};render() {console.log("render");return (<div><h1>{this.state.message}</h1><button onClick={this.changeMessage}>changeMessage</button></div>);}componentDidMount() {console.log("compoentDidMount");}componentDidUpdate() {console.log("compoentDidUpdate");}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

更新数据的时候,render函数会被多次调用。并且再render执行完成后,都会调用componentDidUpdate方法。
在这里插入图片描述

更新数据时的生命周期和componentDidUpdate方法——子组件更新

    <div id="root"></div><script type="text/babel">class MyComponent extends React.Component {constructor(props) {super(props);console.log("constructor");}render() {console.log("render");return (<div><SubComponent /></div>);}componentDidMount() {console.log("compoentDidMount");}componentDidUpdate() {console.log("compoentDidUpdate");}}class SubComponent extends React.Component {state = {message: "this is message",};constructor(props) {super(props);console.log("sub constructor");}changeMessage = () => {this.setState({message: "message changed",});};render() {console.log("sub render");return (<div><h1>{this.state.message}</h1><button onClick={this.changeMessage}>changeMessage</button></div>);}componentDidMount() {console.log("sub compoentDidMount");}componentDidUpdate() {console.log("sub compoentDidUpdate");}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

可以看到。当子组件内容被修改的时候,只会调用,子组件的更新方法,父组件是不会调用更新方法的,也不会调用render。

在这里插入图片描述

更新数据时的生命周期和componentDidUpdate方法——父组件更新

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {message: "this is message",};constructor(props) {super(props);console.log("constructor");}changeMessage = () => {this.setState({message: "message changed",});};render() {console.log("render");return (<div><h1>{this.state.message}</h1><button onClick={this.changeMessage}>changeMessage</button><SubComponent /></div>);}componentDidMount() {console.log("compoentDidMount");}componentDidUpdate() {console.log("compoentDidUpdate");}}class SubComponent extends React.Component {constructor(props) {super(props);console.log("sub constructor");}render() {console.log("sub render");}componentDidMount() {console.log("sub compoentDidMount");}componentDidUpdate() {console.log("sub compoentDidUpdate");}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

父组件内容修改的时候,子组件的render和componentDidUpdate也是会一起调用的,也就是子组件会重新渲染,因为子组件可能依赖了父组件的数据。react选择直接重新渲染子组件。
在这里插入图片描述

组件卸载和componentWillUnmount方法

在组件被销毁的时候,componentWillUnmount会被调用。我们需要在componentWillUnmount做一些回收资源的事情。

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {show: true,};constructor(props) {super(props);// console.log("constructor");}changeShow = () => {this.setState({show: false,});};render() {// console.log("render");const { show } = this.state;return (<div>{show && <SubComponent />}<button onClick={this.changeShow}>changeShow</button></div>);}}class SubComponent extends React.Component {state = {show: true,};constructor(props) {super(props);console.log("sub constructor");}changeShow = () => {this.setState({show: false,});};render() {console.log("sub render");return <div>我是子组件</div>;}componentDidMount() {console.log("sub compoentDidMount");}componentDidUpdate() {console.log("sub compoentDidUpdate");}componentWillUnmount() {console.log("sub componentWillUnmount");}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

不常用的生命周期

不常用也是官方文档说的。
在这里插入图片描述

shouldComponentUpdate方法

如果shouldComponentUpdate返回false,那么组件的render和componentDidUpdate将不会被调用。这个可以用于一些性能优化的地方。禁止一些组件的重新渲染。
从图中也可以很明显看到,render和componentDidUpdate这两个函数是在shouldComponentUpdate之后执行的,如果shouldComponentUpdate返回false,那么后面的生命周期将不再执行。

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {message: "this is message",};constructor(props) {super(props);console.log("constructor");}changeMessage = () => {this.setState({message: "message changed",});};render() {console.log("render");return (<div><h1>{this.state.message}</h1><button onClick={this.changeMessage}>changeMessage</button></div>);}componentDidMount() {console.log("compoentDidMount");}componentDidUpdate() {console.log("compoentDidUpdate");}shouldComponentUpdate() {return true;}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>
getsnapshotbeforeupdate方法

官方文档给了下面的例子。
https://zh-hans.legacy.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate

处理列表滚动的时候可以用到,是伪代码。

  <body><div id="root"></div><script type="text/babel">class ScrollingList extends React.Component {constructor(props) {super(props);this.listRef = React.createRef();}getSnapshotBeforeUpdate(prevProps, prevState) {// 我们是否在 list 中添加新的 items ?// 捕获滚动​​位置以便我们稍后调整滚动位置。if (prevProps.list.length < this.props.list.length) {const list = this.listRef.current;return list.scrollHeight - list.scrollTop;}return null;}componentDidUpdate(prevProps, prevState, snapshot) {// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。//(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)if (snapshot !== null) {const list = this.listRef.current;list.scrollTop = list.scrollHeight - snapshot;}}render() {return <div ref={this.listRef}>{/* ...contents... */}</div>;}}ReactDOM.render(<ScrollingList />, document.getElementById("root"));</script></body>

组件通信

组件通信父传子props

这个其实没什么好讲的,直接使用props就可以了

组件通信子传父

在vue或者uniapp里面,子传父有类似$emit,$on这样的发送接收方法。但在react里面,这些都是没有的。react实现的方式就是纯js的方式,是利用回调函数机制来实现的。
我们需要在子组件上加一个回调方法。

<AddCounter onAdd={(n) => this.handleAdd(n)} />

完整逻辑如下。这里需要注意的地方就是函数的命名规范。例如add方法,你可以在父子组件里面都全部取名叫add,如果你自己能够分得清的话。
命名规范建议是,点击事件用clickXXX,组件回调函数用onXXX,而父组件处理函数用handleXXX。

    <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {counter: 0,};constructor(props) {super(props);console.log("constructor");}handleAdd = (num) => {this.setState({counter: this.state.counter + num,});};render() {const { counter } = this.state;return (<div><div>计数结果是:{counter}</div><AddCounter onAdd={(n) => this.handleAdd(n)} /></div>);}}class AddCounter extends React.Component {constructor(props) {super(props);console.log("constructor");}clickAdd = (n) => {this.props.onAdd(n);};render() {return (<div>{/*要写箭头函数,不能直接执行 直接执行会在在渲染的时候就调用了 */}<button onClick={() => this.clickAdd(1)}>+1</button><button onClick={() => this.clickAdd(5)}>+5</button></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

tabs切换的组件通信案例(简单实现版)

写在一个组件里面,实现基本功能。

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="https://unpkg.com/react@18/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script><!-- Don't use this in production: --><script src="https://unpkg.com/@babel/standalone/babel.min.js"></script><style>.tabs {display: inline-block;padding: 10px;}.active {color: pink;border-bottom: 2px solid pink;}</style></head><body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {tabs: [{name: "电脑",},{name: "手机",},{name: "电视",},],curIndex: 0,};clickTab = (index) => {this.setState({curIndex: index,});};constructor(props) {super(props);console.log("constructor");}render() {const { tabs, curIndex } = this.state;return (<div>{tabs.map((item, index) => {return (<spanclassName={`tabs ${index === curIndex ? "active" : ""}`}key={index}onClick={(e) => this.clickTab(index)}>{item.name}</span>);})}<div>{tabs[curIndex].name}</div></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>
</html>

在这里插入图片描述

tabs切换的组件通信案例(组件封装版)

PureComponent类和memo高阶函数

这个类是React提供的封装好的优化类。只有组件的props或者state发生改变的时候,才会重新渲染组件。源码实现是通过浅层比较实现,也就是只比较对象的第一层。

对于函数式组件来说,是没有生命周期的,也就不能实现类似PureComponent这样的封装效果,但是React提供了memo函数来实现这个功能。

开发的时候,你是一定要用到这两个东西的,不知道的话你一定是一个新手。官方文档性能优化是有提到这两个东西的。

hooks

什么是hooks? (coderwhy)

hooks是react 16.8(2019年)出的新特性。
react有两种形式来创建组件——类式和函数式。在hooks之前类式组件就是react最主流的编程方式。 这个时候,函数式组件是非常鸡肋的,几乎没什么用。因为函数式组件不能保存数据状态,所以只能用于一些简单的展示的场景,传什么数据就展示什么数据(因为只有props是可以用的)。并且函数组件是没有生命周期的。

但因为函数式编程的思想在前端是普遍流行和推崇的。我猜想react团队在设计函数式组件的时候肯定已经想到了这个问题。但可能当时没有想到合适方式实现函数式组件的完整功能。

对于开发者来说,使用类式组件或者函数式组件来开发功能实际上都是无所谓,谁好用就用谁。但设计者为了实现函数式组件可以说是绞尽脑汁。至于设计出来的东西好不好用另说。但函数式组件的这条路是一定要走下去的。

还有一个促使hooks诞生的原因是类式组件存在一些缺点。例如类式不好对功能进行拆分。当然hooks本身是否存在别的缺点我们另说。class概念难以理解以及this的指向问题处理对于初学者来说都是比较麻烦的。

1.hooks是完全可选的,你不用hooks,用类式组件也是完全没有问题的。
2.hooks是100%向后兼容的。hook不包含任何破坏性改动。

3.hooks的代码比类组件相对少一些。

组件插槽

react没有slot这样的节点概念,因为他不需要。

通过props.children实现插槽效果

通过props.children就可以直接实现插槽的效果。

  <body><div id="root"></div><script type="text/babel">class NavBar extends React.Component {state = { isHot: true };render() {const { children } = this.props;return (<div className="content"><div className="left">{children[0]}</div><div className="center">{children[1]}</div><div className="right">{children[2]}</div></div>);}changeWeather = () => {this.setState({ isHot: !this.state.isHot });};}NavBar.propTypes = {name: PropTypes.string.isRequired,age: PropTypes.number,speak: PropTypes.func,};NavBar.defaultProps = {name: "Tom",age: 19,};class Main extends React.Component {render() {return (<NavBar><div>我是左边内容</div><div>我是中间内容</div><div>我是右边内容</div></NavBar>);}}ReactDOM.render(<Main />, document.getElementById("root"));</script></body>

是可以实现插槽效果的。
在这里插入图片描述
但是,如果只有一个元素的时候,children并不是一个数组,而是一个元素对象,这时候通过数组下标取元素就不对了,虽然控制台并不会报错。这是react就是这样设计的,不要问为什么。 存在弊端。

            <NavBar><div>我是左边内容</div>{/*<div>我是中间内容</div><div>我是右边内容</div>*/}</NavBar>

在这里插入图片描述
props.children方式存在的问题:
1.children一会是数组一会是对象,容易出错。
2.children是通过下标来直接取元素的,可读性非常的差,容易取错。

通过props参数实现插槽效果

直接通过参数把元素内容传过去,是不是非常的神奇?
再react里面,直接在props里面传参数就可以实现插槽的效果。参数名称随便取。

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="https://unpkg.com/react@18/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script><!-- Don't use this in production: --><script src="https://unpkg.com/@babel/standalone/babel.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script><style>.content {display: flex;/* align-items: center;justify-content: space-around; */height: 50px;.left {width: 100px;background: pink;}.center {width: 100%;background: lightblue;}.right {width: 100px;background: lightgreen;}}</style></head><body><div id="root"></div><script type="text/babel">class NavBar extends React.Component {render() {const { leftSlot, centerSlot, rightSlot } = this.props;console.log(this.props);return (<div className="content"><div className="left">{leftSlot}</div><div className="center">{centerSlot}</div><div className="right">{rightSlot}</div></div>);}}class Main extends React.Component {render() {return (<NavBarleftSlot={<div>我是左边内容</div>}rightSlot={<div>我是右边内容</div>}centerSlot={<div>我是中间内容</div>}/>);}}ReactDOM.render(<Main />, document.getElementById("root"));</script></body>
</html>

受控组件和非受控组件

受控组件本质上就是表单组件,在react中表单数据交由state管理。这是react自己创造的一个概念。
非受控组件在react中也是指表单组件,只是数据不是state控制的,用户提供直接操作dom来管理表单数据。几乎是不会用的,只是相当于受控组件的一个概念。

受控组件

下面的代码有一个非常重要的特点是,在设置了value的值后,浏览器的输入框是不能再输入内容的,也不能修改内容。这是被react作用到的。正常的html设置了input的value内容还是可以编辑的。

还有一个小细节是label的绑定id的属性是htmlFor,原来是for,这是因为for是关键字。

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {render() {return (<div><label htmlFor="name">姓名:<input id="name" value="Tom" /></label></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

在这里插入图片描述
给input添加onChange事件,这就是受控组件了。我们在state中绑定了value值,这就是react的受控组件的概念了。实际就是给input组件实现了数据绑定。

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {username: "Tom",};handleChange(e) {console.log(e.target.value);this.setState({username: e.target.value,});}render() {return (<div><label htmlFor="name">姓名:<inputid="name"value={this.state.username}onChange={(e) => this.handleChange(e)}/></label></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

表单提交行为和受控组件

下面的代码就是传统表单的提交方式。 这种方式现在已经没人用了。下面的代码在提交表单的时候会刷新页面。

              <form action="/adduser"><label htmlFor="name">姓名:<inputid="name"value={this.state.username}onChange={(e) => this.handleChange(e)}/></label><button type="submit">提交</button></form>

react的表单提交方式:
下面的代码先是取消了action属性。通过拦截onSubmit方法来实现表单的提交。核心的逻辑就是handleSubmit方法。

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {username: "Tom",};handleSubmit(event) {console.log(event);//1.阻止默认行为event.preventDefault();//2.获取控件数据内容console.log(this.state.username);//3.通过axios提交给服务器}handleChange(event) {console.log(event.target.value);this.setState({username: event.target.value,});}render() {return (<div><form onSubmit={(e) => this.handleSubmit(e)}><label htmlFor="name">姓名:<inputid="name"value={this.state.username}onChange={(e) => this.handleChange(e)}/></label><button type="submit">提交</button></form></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

多个受控组件使用同一个函数处理

其实就是一个处理技巧。通过event.target.name来动态获取属性名,当然,需要我们指定元素name属性的值。

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {username: "Tom",password: "123",};handleSubmit(event) {console.log(event);//1.阻止默认行为event.preventDefault();//2.获取控件数据内容console.log(this.state.username, this.state.password);//3.通过axios提交给服务器}handleChange(event) {console.log(event.target.name);this.setState({[event.target.name]: event.target.value,});}render() {return (<div><form onSubmit={(e) => this.handleSubmit(e)}><label htmlFor="name">姓名:<inputid="name"name="username"value={this.state.username}onChange={(e) => this.handleChange(e)}/></label><label htmlFor="name">密码:<inputid="password"type="password"name="password"value={this.state.password}onChange={(e) => this.handleChange(e)}/></label><button type="submit">提交</button></form></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

单个checkbox和多个checkbox

单个checkbox

我们实现一个登录页面的代码,有用户名密码和同意协议。需要注意的是,checkbox使用的属性是checked不是value。

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {username: "Tom",password: "123",isAgree: false,};handleSubmit(event) {console.log(event);//1.阻止默认行为event.preventDefault();//2.获取控件数据内容const { username, password, isAgree } = this.state;console.log(username, password, isAgree);//3.通过axios提交给服务器}handleChange(event) {console.log(event.target.name);this.setState({[event.target.name]: event.target.value,});}handleCheckedChange(event) {this.setState({isAgree: event.target.checked,});}render() {return (<div><form onSubmit={(e) => this.handleSubmit(e)}><label htmlFor="name">姓名:<inputid="name"name="username"value={this.state.username}onChange={(e) => this.handleChange(e)}/></label><br /><label htmlFor="name">密码:<inputid="password"type="password"name="password"value={this.state.password}onChange={(e) => this.handleChange(e)}/></label><br /><label htmlFor="isAgree"><inputid="isAgree"type="checkbox"checked={this.state.isAgree}onChange={(e) => this.handleCheckedChange(e)}/>用户协议</label><button type="submit">提交</button></form></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>
多个checkbox

多个checkbox主要也是表单的一些选项,这是非常常见的。需要注意的是。我们需要定义一个数组来管理多个checkbox的状态。

  <body><div id="root"></div><script type="text/babel">class MyComponent extends React.Component {state = {username: "Tom",password: "123",isAgree: false,hobbies: [{ name: "唱", value: "sing", checked: false },{ name: "跳", value: "dance", checked: false },{ name: "rap", value: "rap", checked: false },],};handleSubmit(event) {console.log(event);//1.阻止默认行为event.preventDefault();//2.获取控件数据内容const { username, password, isAgree, hobbies } = this.state;const selectedHobbies = hobbies.filter((item) => item.checked).map((item) => item.value);console.log(username, password, isAgree);console.log(selectedHobbies);//3.通过axios提交给服务器}handleChange(event) {console.log(event.target.name);this.setState({[event.target.name]: event.target.value,});}handleCheckedChange(event) {this.setState({isAgree: event.target.checked,});}handleHobbiesChange(event, index) {const hobbies = [...this.state.hobbies];hobbies[index].checked = event.target.checked;this.setState({hobbies,});}render() {const { username, password, isAgree, hobbies } = this.state;return (<div><form onSubmit={(e) => this.handleSubmit(e)}><label htmlFor="name">姓名:<inputid="name"name="username"value={username}onChange={(e) => this.handleChange(e)}/></label><br /><label htmlFor="password">密码:<inputid="password"type="password"name="password"value={password}onChange={(e) => this.handleChange(e)}/></label><br />{hobbies.map((item, index) => {return (<label htmlFor={item.value} key={index}><inputid={item.value}type="checkbox"checked={item.checked}onChange={(e) => this.handleHobbiesChange(e, index)}/>{item.name}</label>);})}<br /><label htmlFor="isAgree"><inputid="isAgree"type="checkbox"checked={isAgree}onChange={(e) => this.handleCheckedChange(e)}/>用户协议</label><br /><button type="submit">提交</button></form></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

在这里插入图片描述

高阶函数和高阶组件

高阶函数回顾

什么是高阶函数。一个函数接收一个函数作为参数或者返回一个函数,那个这个函数就是高阶函数。

高阶组件(High Order-Component HOC)

这是react官方给的定义。
官方定义: 高阶组件是参数为组件,返回值为新组件的函数。
注意: 虽然叫高阶组件,但实际上是函数。
接收一个组件作为他的参数。

高阶组件简单例子

高阶组件的主要意义就是对传入的组件进行拦截,这样,我们就可以对传入的组件做一些额外操作了。
下面的代码就是一个简单的高阶组件,虽然没有什么实际意义。

  <body><div id="root"></div><script type="text/babel">//   import { PureComponent } from "react";class FooComponent extends React.PureComponent {render() {return <div></div>;}}function enhanceComponent(OldComponent) {class NewComponent {render() {return <OldComponent name="commonName" />;}}return NewComponent;}ReactDOM.render(<FooComponent />, document.getElementById("root"));const newComponent = enhanceComponent(FooComponent);console.log(newComponent);</script></body>

高阶组件的应用场景一:props

下面的代码可以给每个组件注入一个userInfo到各自的props里面。我们需要把自身的props也传进去。

还是有点用的,如果你需要给某些组件插入一些测试数据的话。

  <body><div id="root"></div><script type="text/babel">//   import { PureComponent } from "react";class MyComponent extends React.PureComponent {render() {return (<div><Home /><Profile /><Find /></div>);}}const Home = enhanceComponent(function (props) {return <div>Home {props.name}</div>;});const Profile = enhanceComponent(function (props) {return <div>Profile {props.name}</div>;});const Find = enhanceComponent(function Find(props) {return <div>Find {props.name}</div>;});function enhanceComponent(OldComponent) {class NewComponent extends React.PureComponent {state = {userInfo: {name: "Tom",age: 10,},};render() {return <OldComponent {...this.props} {...this.state.userInfo} />;}}return NewComponent;}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

高阶组件的应用场景二:真实应用场景 Context

我们在使用Context的时候,需要写类似下面的代码,这段代码看起来非常的繁琐,而且每次都需要这样写非常的恶心。通过高阶函数可以转变成即简单又优雅的实现方式。

  <body><div id="root"></div><script type="text/babel">const ThemeContext = React.createContext();class MyComponent extends React.PureComponent {render() {return (<div><ThemeContext.Provider value={{ color: "red", size: 30 }}><Product /></ThemeContext.Provider></div>);}}class Product extends React.PureComponent {render() {return (<div><ThemeContext.Consumer>{(value) => {return <div>{value.size}</div>;}}</ThemeContext.Consumer></div>);}}ReactDOM.render(<MyComponent />, document.getElementById("root"));</script></body>

相关文章:

React基础知识一

写的东西太多了&#xff0c;照成csdn文档编辑器都开始卡顿了&#xff0c;所以分篇写。 1.安装React 需要安装下面三个包。 react:react核心包 react-dom:渲染需要用到的核心包 babel:将jsx语法转换成React代码的工具。&#xff08;没使用jsx可以不装&#xff09;1.1 在html中…...

游戏行业趋势:“AI、出海、IP”大热下,如何提升竞争力?

游戏&#xff1a;新品供给影响业绩释放节奏&#xff0c;后续游戏新品逐步上线&#xff0c;或驱动板块业绩修复 2024年前三季度A股游戏板块实现营业收入681.8亿元&#xff0c;同比增长5.1%&#xff0c;实现归母净利润73.3亿元&#xff0c;同比下滑30.4%&#xff0c;或主要受 20…...

shell--第一次作业

1.接收用户部署的服务名称 # 脚本入口 read -p "请输入要部署的服务名称&#xff1a;" service_name 2.判断服务是否安装 # 判断服务是否安装 if rpm -q "$service_name" &>/dev/null; then echo "服务 $service_name 已安装。" 已…...

Rust:原子操作 AtomicBool

在 Rust 中&#xff0c;你可以使用 std::sync::atomic 模块来进行原子操作。原子操作在多线程环境中特别有用&#xff0c;因为它们可以确保操作的原子性和可见性&#xff0c;从而避免数据竞争和其他并发问题。 为了读取和设置布尔值&#xff0c;你可以使用 AtomicBool 类型。以…...

深入浅出学算法002-n个1

任务内容 Description 由n个1组成的整数能被K&#xff08;K<10000)整除&#xff0c;n至少为多少&#xff1f; Input 多组测试数据&#xff0c;第一行输入整数T,表示组数 然后是T行&#xff0c;每行输入1个整 数代表K Output 对于每组测试数据输出1行&#xff0c;值为n Sampl…...

GPT1.0 和 GPT2.0 的联系与区别

随着自然语言处理技术的飞速发展&#xff0c;OpenAI 提出的 GPT 系列模型成为了生成式预训练模型的代表。作为 GPT 系列的两代代表&#xff0c;GPT-1 和 GPT-2 虽然在架构上有着继承关系&#xff0c;但在设计理念和性能上有显著的改进。本文将从模型架构、参数规模、训练数据和…...

STM32F103 GPIO和串口实战

本节我们将会对STM32F103的硬件资源GPIO和串口进行介绍。 一、GPIO 1.1 电路原理图 LED电路原理图如下图所示&#xff1a; 其中&#xff1a; LED1连接到PA8引脚&#xff0c;低电平点亮&#xff1b;LED2连接到PD2引脚&#xff0c;低电平点亮&#xff1b; 1.2 GPIO引脚介绍 STM32…...

Go 并发

Go 并发 Go 语言,自2009年发布以来,以其独特的并发模型和简洁的语法在编程界崭露头角。Go 语言的并发机制是其最大的亮点之一,它通过轻量级的线程——goroutine,以及通道(channel)和同步原语,为开发者提供了一种高效、易用的并发编程方式。 Goroutine:Go 语言的并发基…...

C语言数据结构与算法--简单实现栈的出栈与入栈

&#xff08;一&#xff09;栈的基本概念 栈(Stack)是限定仅在表尾进行插入和删除操作的线性表&#xff0c;如铁路调度。如下 图&#xff1a; &#xff08;二&#xff09;栈的的表现形式 栈有两种表示形式&#xff1a;栈的表示和实现、栈的 链式表示。 1&#xff0e;栈的表示和…...

单片机智能家居火灾环境安全检测-分享

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 传统的火灾报警系统大多依赖于简单的烟雾探测器或温度传感器&#xff0c;…...

python FastAPI 后台运行

最近需要用python把AI的能力封装起来&#xff0c;通过暴露API的方式供别的服务来调用。整体的想法是&#xff0c;选择对应接口&#xff0c;接口读取传入的sql语句&#xff0c;自动去数据库读取数据&#xff0c;运算后将结果在存放在数据库中。 搭建FastAPI框架&#xff0c;由于…...

Java开发者必备:23种设计模式全面解析

文章目录 一、创建型模式1、工厂模式简单工厂工厂方法 2、抽象工厂模式3、原型模式4、建造者模式5、单例模式 二、结构型模式1、适配器模式2、桥接模式3、组合模式4、装饰模式5、外观模式6、享元模式7、代理模式 三、行为型模式1、解释器模式2、模板方法模式3、策略模式4、观察…...

编译OpenWrt步骤

实验平台搭建 硬件平台&#xff1a;hilink-7628n核心板 宿主机系统&#xff1a;ubuntu20.04 server 宿主机安装所需工具&#xff1a; binutils bzip2 diff find flex gawk gcc-6 getopt grep install libc-dev libz-dev make4.1 perl python3.7 rsync subversion unzip whi…...

Linux:confluence8.5.9的部署(下载+安装+pojie)离线部署全流程 遇到的问题

原文地址Linux&#xff1a;confluence8.5.9的部署&#xff08;下载安装破ji&#xff09;离线部署全流程_atlassian-agent-v1.3.1.zip-CSDN博客 背景&#xff1a;个人使用2核4g 内存扛不住 总是卡住&#xff0c;但是流程通了所以 直接公司开服务器干生产 个人是centos7 公司…...

✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本

api.js //封装ajax方法 import $g from "../sg";//vue项目使用 import $ from jquery;//(提示&#xff1a;原生开发页面请前往https://jquery.com下载最新版jQuery) import { Message } from "element-ui";//element项目使用 // import axios from "…...

axios 请求跨域问题

文章目录 1. 使用场景2. 解决办法 1. 使用场景 ① 编写后端测试接口&#xff0c;Vue-CLI 的默认端口为 8080&#xff0c;所以为避免端口冲突&#xff0c;我们后端的端口号换成 8081。 ② 前端通过 axios 向后端服务发起请求。 <script> import axios from axios export…...

什么是 Faiss?

好的&#xff0c;我来详细解释 Faiss&#xff0c;它的用途、使用场景&#xff0c;以及如何安装和使用。 什么是 Faiss&#xff1f; Faiss 是由 Facebook AI Research 开发的一个开源库&#xff0c;专门用于高效的相似性搜索和聚类。它非常擅长在高维向量空间中进行快速搜索&a…...

24.UE5枚举,怪物分类,龙卷风技能

2-26 枚举、怪物分类、龙旋风技能、掉落概率_哔哩哔哩_bilibili 目录 1.枚举 1.1枚举类型的创建 1.2 将枚举类型绑定到怪物蓝图上 1.3枚举类型的使用 1.3.1创建新的掉落物 1.3.2更改怪物掉落逻辑 2.龙卷风技能 2.1输入映射 2.2龙卷风发射物的创建 2.3龙卷风伤害逻辑…...

什麼是ISP提供的公共IP地址?

公共IP地址是ISP分配給設備或網路的全球唯一地址。此地址允許通過互聯網識別和訪問設備。ISP提供的公共IP地址具有幾個關鍵特徵&#xff1a; 1.每個公網IP在全球網路內都是唯一的&#xff0c;避免衝突。 2. 公共 IP 地址對其他網路可見&#xff0c;並且可用於地理定位設備。 …...

git操作总结

git基本知识 工作区域 远程仓库&#xff1a; 就是我们托管在github或者其他代码托管平台上的仓库。本地仓库&#xff1a; 就是在我们本地通过git init命令初始化的新建的仓库。工作区&#xff1a; 就是我们写代码、编辑文件的地方。暂存区&#xff1a; 当工作区的内容写好了之…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...