写的东西太多了,照成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="请输入内容" /> <button onClick={this.showData}>点击显示内容</button> <input id="input2" placeholder="" onBlur={this.showData2} /> </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="请输入内容" /> <button onClick={this.showData}>点击显示内容</button> <input ref='input2' id="input2" placeholder="" onBlur={this.showData2} /> </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="请输入内容" /> <button onClick={this.showData}>点击显示内容</button> <input ref={e=> this.input2 = e} id="input2" placeholder="" onBlur={this.showData2} /> </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="请输入内容" /> <button onClick={this.showData}>点击显示内容</button> </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="请输入内容" /> <button onClick={this.showData}>点击显示内容</button> </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="请输入内容" /> <button onClick={this.showData}>点击显示内容</button> <input ref={this.myRef2} onBlur={this.showData2} id="input1" placeholder="请输入内容" /> </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>