第二节:jsx语法深度剖析和jsx本质的探究
一. jsx说明及用法
1. 什么jsx?
JSX是一种JavaScript的语法扩展(extension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;
它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);
2. 为什么React选择使用jsx?
(1) React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
比如UI需要绑定事件(button、a原生等等)
比如UI中需要展示数据状态
比如在某些状态发生改变时,又需要改变UI
(2) 他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component)
3. JSX的书写规范
(1) JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素(或者使用后面书写Fragment,可以包裹多个根元素);
(2) 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;
(3) JSX中的标签可以是单标签,也可以是双标签;
💝 注意:如果是单标签,必须以 /> 结尾;
(4) 使用 {xxx} 符号,渲染变量、方法等 PS:类似于vue的 {{xxxx}}
(5) 注释的写法:{/* 我是注释内容 */}
render() {
let { msg } = this.state;
return (
<div>
{/* 我是注释 */}
<h2>{msg}</h2>
</div>
);
}
4. JSX中插入变量
情况一:
当变量是Number、String、Array类型时,可以直接显示
情况二:
当变量是null、undefined、Boolean类型时,内容为空(即:什么也不显示);
✓ 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
✓ 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
情况三:
Object对象类型不能作为子元素(🎉 报错:Objects are not valid as a React child !!!)
// 1.1 构造函数
constructor() {
super();
this.state = {
msg: 'hello ypf',
myName: 18,
myHobbies: ['购物', '上网', '打游戏'],
myOthers: [null, undefined, true],
myObj: { name: 'lmr', age: 19 }
}
}
// 1.2 渲染方法
render() {
let { msg, myName, myHobbies, myOthers, myObj } = this.state;
return (
<div>
{ /* 1 string类型 */}
<h2>{msg}</h2>
{ /* 2 number类型 */}
<h2>{myName}</h2>
{ /* 3 string类型 */}
<h2>{myHobbies}</h2>
{ /* 4 null、undefined、Boolean */}
{ /* 4.1 默认不显示 */}
<h2>{myOthers[0]},{myOthers[1]},{myOthers[2]}</h2>
{ /* 4.2 转换成string才能显示 */}
<h2>{myOthers[0] + ""},{String(myOthers[1])},{myOthers[2].toString()}</h2>
{ /* 5 Obejct类型报错 */}
{ /* <h2>{myObj}</h2> */}
{ /* 6 三元运算符 */}
<h2>{myName >= 18 ? '成年人' : '未成年人'}</h2>
{ /* 7 函数调用 */}
<h2>{this.getMsg()}</h2>
</div>
);
}
// 普通函数
getMsg() {
return '我是一个普通函数'
}
5. JSX嵌入表达式
运算表达式、三元运算符、执行一个函数
详见上述代码
6. 属性绑定
(1). 基本属性
比如元素都会有title属性、img元素会有src属性、a元素会有href属性
用法: <h1 xxx={}><h1/>
(2). class绑定
用法:<h2 className={}></h2>
A. 直接字符串拼接
B. 放到数组里,然后使用join方法转换成空格分隔的字符串
(3). style绑定
用法: <h2 style={{color: "red", fontSize: "30px"}}>呵呵呵呵</h2>
<h2 style={styleObj}>呵呵呵呵</h2>
💝 PS: 绑定的内容是一个对象!! 上述第一个h2标签, 第二个{}, 代表的是对象。
(4). 自定义多个属性的绑定
可以使用 <h2 {...obj}></h2> 写法,进行一次性绑定多个属性
查看代码
class App extends React.Component {
// 1.1 构造函数
constructor() {
super();
this.state = {
msg: '基本属性绑定',
myTitle: 'hhh',
imgUrl: 'https://www.baidu.com/img/bdlogo.png',
myHref: 'https://www.baidu.com'
}
}
// 1.2 渲染方法
render() {
let { msg, myTitle, imgUrl, myHref } = this.state;
// 2. 属性绑定
// 直接字符串
let class1 = "abc def hgk";
let classList = ['abc', 'def', 'hgk']
// 3. style绑定
let styleObj = { color: "red", fontSize: "30px" };
// 4. 自定义多个属性的绑定
let myObj = { name: "ypf", age: 18, school: "gx" }
return (
<div>
{ /* 1. 基本属性 */}
<h2 title={myTitle}>{msg}</h2>
<img src={imgUrl} alt="" />
<a href={myHref}>点我跳转</a>
{ /* 2. class属性 */}
<h2 className={class1}>class属性绑定1</h2>
<h2 className={classList.join(" ")}>class属性绑定2</h2>
{ /* 3. style属性 */}
<h2 style={{ color: "red", fontSize: "30px" }}>style属性绑定1</h2>
<h2 style={styleObj}>style属性绑定2</h2>
{ /* 4. 自定义多个属性 */}
<h2 {...myObj}>自定义绑定多个属性</h2>
</div>
);
}
}
二. jsx核心用法
1. 事件绑定
(1). 用法
React 事件的命名采用 "小驼峰式"(camelCase),而不是纯小写;我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行
比如: <h2 onClick={xxxx}>xxx<h2/>
(2). this的绑定问题
默认在onClick中直接调用一个方法,方法中的this是undefined,为什么呢?
(1). 普通模式下:默认绑定,this指向windows
(2). Bable转换,即严格模式下,this则为undefined
react中如果直接 onClick={this.btnclick}, 原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;而它内部调用时,并不知道要如何绑定正确的this;
详见 默认情况和babel的案例 以及 默认按钮1
查看代码 <script>
// 测试this的问题--普通模式下
{
console.log('-------------测试this的问题--普通模式下-----------------');
const obj = {
name: 'ypf',
foo: function () {
console.log("foo:", this);
}
}
obj.foo(); //这里的this指向obj
const config = {
onClick: obj.foo
}
const click = config.onClick;
click(); //this为windows
}
</script>
<script type="text/babel">
// 测试this的问题--严格模式下
{
console.log('-------------测试this的问题--严格模式下-----------------');
const obj = {
name: 'ypf',
foo: function () {
console.log("foo:", this);
}
}
obj.foo(); //这里的this指向obj
const config = {
onClick: obj.foo
}
const click = config.onClick;
click(); //this为undefined, 虽然是默认调用,但是babel会转换成严格模式,严格模式下的this就是undefined
}
</script>
(3). 解决this的3种方案
方案1:使用bind显示的绑定this,有下面两种写法
(1). 每次调用的时候绑定,eg: onClick={this.btnClick.bind(this)}
(2). 在constructor中提前绑定, eg: this.btnClick=this.btnClick.bind(this)
方案2:使用 ES6 class fields 语法
借助类中的field字段,将方法声明成箭头函数,由于箭头函数中没有this,则去外层作用域中找,外层作用域的this就是app实例,正好符合使用
btnClick4 = () =>{}
方案3:事件监听时传入箭头函数 【强烈推荐】
onClick={() => this.btnClick1()}
直接传入一个箭头函数,this则去外层作用域找,即render函数中的this,正好符合使用
查看代码 <script type="text/babel">
// 1. 定义跟组件
class App extends React.Component {
// 1.1 构造函数
constructor() {
super();
this.state = {
msg: 'this绑定的几种情况'
}
// 提前绑定
this.btnClick3 = this.btnClick3.bind(this);
}
//实例方法
btnClick1() {
console.log('----------btnClick1---------')
console.log('btnClick1---this:', this)
}
btnClick2() {
console.log('----------btnClick2---------')
console.log('btnClick2---this:', this)
}
btnClick3() {
console.log('----------btnClick3---------')
console.log('btnClick3---this:', this)
}
// ES6中的Class-field
btnClick4 = () => {
console.log('----------btnClick4---------')
console.log('btnClick4---this:', this)
}
// 1.2 渲染方法
render() {
let { msg } = this.state;
return (
<div>
<h2>{msg}</h2>
<p><button onClick={this.btnClick1}>1.默认</button></p>
{/* 方案1:bind绑定 */}
<p><button onClick={this.btnClick2.bind(this)}>2-1.每次调用bind</button></p>
<p><button onClick={this.btnClick3}>2-2.提前bind</button></p>
{/* 方案2:ES6中的Class-field */}
<p><button onClick={this.btnClick4}>3.ES6中的Class-field</button></p>
{/* 方案3:事件监听时候,直接传入一个箭头函数 */}
<p><button onClick={() => this.btnClick1()}>4.ES6中的Class-field</button></p>
</div>
);
}
}
// 2. 创建root并渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />)
</script>
2. 参数传递
在执行事件函数时,有可能我们需要获取一些参数信息:比如event对象、其他参数
(1). event对象默认传递
方案1: 箭头函数的模式,event需要显式传递,可以传递到任何位置
方案2: 使用this.btnClick.bind(this)的模式,event默认传递,默认在最后一个参数的位置
(2). 额外参数的传递
方案1:箭头函数的模式,因为可以随意传递参数,非常简单,所见即所得,event对象想传递在哪都可以 【推荐】
方案2:使用this.btnClick.bind(this,p1,p2)的模式,event默认传递在最后一个参数的位置,这就要求btnClick的接收参数需要最后一个是event 【不推荐】
查看代码 <script type="text/babel">
// 1. 定义跟组件
class App extends React.Component {
// 1.1 构造函数
constructor() {
super();
this.state = {
msg: 'hello ypf'
}
}
// 实例方法
btnClick(event, name, age) {
console.log('--------------btnClick------------------');
console.log('btnClick--this:', this);
console.log('event:', event);
console.log('name:', name);
console.log('age:', age);
}
// 1.2 渲染方法
render() {
let { msg } = this.state;
return (
<div>
<h2>{msg}</h2>
{/* 1.event参数的传递 */}
<p><button onClick={this.btnClick.bind(this)}>参数传递1</button></p>
<p><button onClick={(event) => this.btnClick(event)}>参数传递2</button></p>
{/* 2.额外参数的传递 */}
{/* 2.1 推荐写法 */}
<p><button onClick={(event) => this.btnClick(event, 'ypf', 18)}>额外参数传递1</button></p>
{/* 2.2 不推荐写法,因为event默认会放在最后一个参数上传递
点击按钮的结果:
event:'ypf'
name: 18
age: event对象了
*/}
<p><button onClick={this.btnClick.bind(this, 'ypf', 18)}>额外参数传递2</button></p>
</div>
);
}
}
3. 条件渲染
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容,在vue中通过 v-if 或 v-show来处理,而在react中,所有的条件判断都和普通的JavaScript代码一致;
方式一:条件判断语句
适合逻辑较多的情况
方式二:三元运算符
适合逻辑比较简单
方式三:与运算符&&
适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;
方式四: v-show的效果
主要是控制display属性为:none 还是 display
查看代码 // 1. 定义跟组件
class App extends React.Component {
// 1.1 构造函数
constructor() {
super();
this.state = {
msg: 'hello ypf',
isReady: false,
friend: undefined,
isShow: true
}
}
// 实例方法
myChange() {
this.setState({
isShow: !this.state.isShow
})
}
// 1.2 渲染方法
render() {
let { msg, isReady, friend, isShow } = this.state;
// 方式1:直接使用if判断
let showElement = null;
if (isReady) {
showElement = <h2>你好啊1</h2 >;
} else {
showElement = <h2>你好啊2</h2 >;
}
return (
<div>
<h2>{msg}</h2>
{/* 方式1: 根据条件给变量赋值不同的内容 */}
<div>{showElement}</div>
{/* 方式2: 三元运算符 */}
<div>{isReady ? <h2>你好啊1</h2 > : <h2>你好啊2</h2 >}</div>
{/* 方式3: &&逻辑与运算 */}
{/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */}
<div>{friend && <span>{friend.name + "" + friend.age}</span>}</div>
{/* 方式4: 模拟v-show的效果 , 下面的第二个{},代表是一个对象啊*/}
<div><button onClick={() => this.myChange()}>切换</button></div>
<div style={{ display: isShow ? 'block' : 'none' }}>{msg}</div>
</div>
);
}
}
4. 小案例
A 事件绑定--点击变色案例
(1).核心思路
点击的时候设置currentIndex等于当前的index,然后class中通过判断currentIndex是否等于index,决定是否添加red属性
(2).分享三种写法
A. 全部写到return里
B. 将liElement抽离出来
C. 将map的回调抽离出来
详见代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>点击变色案例</title> <style> ul>li { margin-bottom: 5px; cursor: pointer; } .red { color: red; } </style> </head> <body> <!-- 根 --> <div id="root"></div> <!-- 引入三个必备库 --> <script src="../lib/react.js"></script> <script src="../lib/react-dom.js"></script> <script src="../lib/babel.js"></script> <script type="text/babel"> // 1. 定义跟组件 class App extends React.Component { // 1.1 构造函数 constructor() { super(); this.state = { msg: '点击变色案例', movies: ["星际穿越", "盗梦空间", "大话西游", "流浪地球"], currentIndex: -1 } } //点击变色方法 myClick(index) { this.setState({ currentIndex: index }) } // 1.2 渲染方法 render() { let { msg, movies, currentIndex } = this.state; //写法1: 全部写在return里 /* { return ( <div> <h2>{msg}</h2> <ul> {movies.map((item, index) => ( <li key={index} onClick={() => this.myClick(index)} className={index == currentIndex ? "red" : ""} >{item}</li> ) )} </ul> </div> ); } */ // 写法2:抽离出来整个li列表 /* { let liElement = movies.map((item, index) => ( <li key={index} onClick={() => this.myClick(index)} className={index == currentIndex ? "red" : ""} >{item}</li> )) return ( <div> <h2>{msg}</h2> <ul> {liElement} </ul> </div> ); } */ // 写法3:将map的回调抽离出来 { let liElementFunc = (item, index) => ( <li key={index} onClick={() => this.myClick(index)} className={index == currentIndex ? "red" : ""} >{item}</li> ); return ( <div> <h2>{msg}</h2> <ul> {movies.map(liElementFunc)} </ul> </div> ); } } } // 2. 创建root并渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")); root.render(<App />) </script> </body> </html>
B 条件渲染列表
可以分步编写,也可以一次性链式编程直接在return里写完,主要涉及到几个高阶函数。
filter:用于过滤
slice:是一个纯函数,且会产生一个新的数组,不会对原数组产生影响。 范围是前闭后开:[start,end)
map:遍历元素,组合成一个新的数组返回,新的数组中的内容可以自行添加,不仅仅局限于遍历对象中数据。
ps:补充一个小细节, 如果加上{}, 就需要加return了
list.map(item=>(<h1>{xxx}</h1>))
list.map(item=>{return (<h1>{xxx}</h1>)})
代码分享:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React模板</title> <style> .myDiv { width: 200px; height: 150px; border: 1px solid black; margin: 5px; padding: 0 10px; } </style> </head> <body> <!-- 根 --> <div id="root"></div> <!-- 引入三个必备库 --> <script src="../lib/react.js"></script> <script src="../lib/react-dom.js"></script> <script src="../lib/babel.js"></script> <script type="text/babel"> // 1. 定义跟组件 class App extends React.Component { // 1.1 构造函数 constructor() { super(); this.state = { msg: '下面是符合条件的学生信息', studentList: [ { id: 111, name: "ypf1", score: 100 }, { id: 222, name: "ypf2", score: 101 }, { id: 333, name: "ypf3", score: 102 }, { id: 444, name: "ypf4", score: 103 }, { id: 555, name: "ypf5", score: 104 }, ] } } // 1.2 渲染方法 render() { let { msg, studentList } = this.state; // 需求:求分数大于100分的任意两个学生的信息 // 写法1--逐条业务来写 { let stu1 = studentList.filter(item => item.score > 100); let stu2 = stu1.slice(0, 2); // 范围是 [start,end), 前闭后开 let stuElement = stu2.map(item => ( <div key={item.id} className="myDiv"> <p>编号:{item.id}</p> <p>姓名:{item.name}</p> <p>分数:{item.score}</p> </div> ) ); return ( <div> <h2>{msg}</h2> {stuElement} </div> ); } // 写法2--直接写到return里, 一步到位,链式编程 /* { return ( <div> <h2>{msg}</h2> {studentList.filter(item => item.score > 100).slice(0, 2).map(item => ( <div key={item.id} className="myDiv"> <p>编号:{item.id}</p> <p>姓名:{item.name}</p> <p>分数:{item.score}</p> </div> ))} </div> ); } */ } } // 2. 创建root并渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")); root.render(<App />) </script> </body> </html>
三. 综合案例--购物车
1 修改数据常见写法
(1). 先把要求改的数据浅拷贝一份
(2). 对浅拷贝的这个数据进行修改
(3). 调用setState方法,用修改后的数据进行覆盖
2. 核心功能
增加:可以根据id 或者 index直接查找到数据,然后修改,然后赋值。
减少:同增加
删除:可以利用filter过滤,或者利用 splice方法对原数组进行删除。
PS: splice(start,deleteCount), 表示从start的位置开始,删除deleteCount个元素
splice的各种用法,详见:https://www.cnblogs.com/yaopengfei/p/15870038.html
求和:使用reduce直接求和
显示:根据有无数据,封装两个方法 renderBookList 和 renderBookEmpty
版本1代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>购物车案例-版本1</title> <style> table { border-collapse: collapse; text-align: center; } thead { background-color: #f2f2f2; } td, th { padding: 10px 16px; border: 1px solid #aaa; } .btn1 { margin-right: 5px; } .btn2 { margin-left: 5px; } </style> </head> <body> <!-- 根 --> <div id="root"></div> <!-- 引入三个必备库 --> <script src="../lib/react.js"></script> <script src="../lib/react-dom.js"></script> <script src="../lib/babel.js"></script> <!-- 引入数据 --> <script src="./data.js"></script> <script type="text/babel"> // 1. 定义跟组件 class App extends React.Component { // 1.1 构造函数 constructor() { super(); this.state = { msg: 'hello ypf', bookList: books } } // 实例方法 // 增加 myAdd(id) { // 通常在react中,不推荐直接去修改原先那个this.state.books, 而是先做一个浅拷贝,然后修改这个数据,最后调用setState赋值给它并渲染 let tempBookList = [...this.state.bookList]; //浅拷贝 tempBookList.forEach(item => { if (item.id === id) item.count++ }); // 显式的调用setState函数,会自动去调用下面的render函数 this.setState({ bookList: tempBookList }) } // 减少 myReduce(id) { let tempBookList = [...this.state.bookList]; //浅拷贝 for (const item of tempBookList) { if (item.id === id) item.count-- } // 修改渲染 this.setState({ bookList: tempBookList }) } // 删除 myDelete(id) { this.setState({ bookList: this.state.bookList.filter(item => item.id != id) }) } // 总和 myTotalPrice() { let total = this.state.bookList.reduce((preValue, currentValue) => { return preValue + currentValue.price * currentValue.count; }, 0); return total; } // 1.2 渲染方法 render() { let { msg, bookList } = this.state; return ( <div> <table> <thead> <tr> <th>序号</th> <th>书籍名称</th> <th>出版日期</th> <th>价格</th> <th>购买数量</th> <th>操作</th> </tr> </thead> <tbody> { bookList.map((item, index) => ( <tr key={item.id}> <td>{item.id}</td> <td>{item.name}</td> <td>{item.date}</td> <td>¥{item.price}</td> <td> <button className="btn1" onClick={() => this.myReduce(item.id)} disabled={item.count <= 0}>-</button> {item.count} <button className="btn2" onClick={() => this.myAdd(item.id)}>+</button> </td> <td><button onClick={() => this.myDelete(item.id)}>删除</button></td> </tr> )) } </tbody> </table> <h2>总价格:${this.myTotalPrice()}</h2> </div> ); } } // 2. 创建root并渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")); root.render(<App />) </script> </body> </html>
版本2代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>购物车案例-版本1</title> <style> table { border-collapse: collapse; text-align: center; } thead { background-color: #f2f2f2; } td, th { padding: 10px 16px; border: 1px solid #aaa; } .btn1 { margin-right: 5px; } .btn2 { margin-left: 5px; } </style> </head> <body> <!-- 根 --> <div id="root"></div> <!-- 引入三个必备库 --> <script src="../lib/react.js"></script> <script src="../lib/react-dom.js"></script> <script src="../lib/babel.js"></script> <!-- 引入数据 --> <script src="./data.js"></script> <script type="text/babel"> // 1. 定义跟组件 class App extends React.Component { // 1.1 构造函数 constructor() { super(); this.state = { msg: 'hello ypf', bookList: books } } // 实例方法 // 增加 myAdd(id) { // 通常在react中,不推荐直接去修改原先那个this.state.books, 而是先做一个浅拷贝,然后修改这个数据,最后调用setState赋值给它并渲染 let tempBookList = [...this.state.bookList]; //浅拷贝 tempBookList.forEach(item => { if (item.id === id) item.count++ }); // 显式的调用setState函数,会自动去调用下面的render函数 this.setState({ bookList: tempBookList }) } // 减少 myReduce(id) { let tempBookList = [...this.state.bookList]; //浅拷贝 for (const item of tempBookList) { if (item.id === id) item.count-- } // 修改渲染 this.setState({ bookList: tempBookList }) } // 删除 myDelete(id) { this.setState({ bookList: this.state.bookList.filter(item => item.id != id) }) } // 总和 myTotalPrice() { let total = this.state.bookList.reduce((preValue, currentValue) => { return preValue + currentValue.price * currentValue.count; }, 0); return total; } // 1.2 渲染方法 render() { let { msg, bookList } = this.state; return ( <div> <table> <thead> <tr> <th>序号</th> <th>书籍名称</th> <th>出版日期</th> <th>价格</th> <th>购买数量</th> <th>操作</th> </tr> </thead> <tbody> { bookList.map((item, index) => ( <tr key={item.id}> <td>{item.id}</td> <td>{item.name}</td> <td>{item.date}</td> <td>¥{item.price}</td> <td> <button className="btn1" onClick={() => this.myReduce(item.id)} disabled={item.count <= 0}>-</button> {item.count} <button className="btn2" onClick={() => this.myAdd(item.id)}>+</button> </td> <td><button onClick={() => this.myDelete(item.id)}>删除</button></td> </tr> )) } </tbody> </table> <h2>总价格:${this.myTotalPrice()}</h2> </div> ); } } // 2. 创建root并渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")); root.render(<App />) </script> </body> </html>
四. jsx原理
1. jsx的本质
(1). 实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。 所有的jsx最终都会被转换成React.createElement的函数调用。
(2). createElement需要传递三个参数:
◼ 参数一:type
当前ReactElement的类型;
如果是标签元素,那么就使用字符串表示 “div”; 如果是组件元素,那么就直接使用组件的名称;
◼ 参数二:config
所有jsx中的属性都在config中以对象的属性和值的形式存储; 比如传入className作为元素的class;
◼ 参数三:children
存放在标签中的内容,以children数组的方式进行存储;
当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方
(3).可以在babel的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react
转换代码详见截图
2. 自己来编写React.createElement代码
我们就没有通过jsx来书写了,界面依然是可以正常的渲染。 另外,在这样的情况下,你还需要babel相关的内容吗?不需要了
✓ 所以,type="text/babel"可以被我们删除掉了;
✓ 所以,<script src="../react/babel.min.js"></script>可以被我们删除掉了;
改造代码:将上述babel转换的代码 /*#__PURE__*/_jsxs 改成: react.createElement
代码分享:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计数器</title> </head> <body> <div id="root"></div> <!-- 引入三个必备库 --> <script src="../lib/react.js"></script> <script src="../lib/react-dom.js"></script> <script> // 1.定义App根组件 class App extends React.Component { constructor() { super() this.state = { message: "Hello World" } } render() { const { message } = this.state // 用的是原生的createElement ,所以根本不需要babel const element = React.createElement( "div", null, /*#__PURE__*/ React.createElement( "div", { className: "header" }, "Header" ), /*#__PURE__*/ React.createElement( "div", { className: "Content" }, /*#__PURE__*/ React.createElement("div", null, "Banner"), /*#__PURE__*/ React.createElement( "ul", null, /*#__PURE__*/ React.createElement( "li", null, "\u5217\u8868\u6570\u636E1" ), /*#__PURE__*/ React.createElement( "li", null, "\u5217\u8868\u6570\u636E2" ), /*#__PURE__*/ React.createElement( "li", null, "\u5217\u8868\u6570\u636E3" ), /*#__PURE__*/ React.createElement( "li", null, "\u5217\u8868\u6570\u636E4" ), /*#__PURE__*/ React.createElement("li", null, "\u5217\u8868\u6570\u636E5") ) ), /*#__PURE__*/ React.createElement( "div", { className: "footer" }, "Footer" ) ); console.log(element) return element } } // 2.创建root并且渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")) root.render(React.createElement(App, null)) </script> </body> </html>
3. 虚拟Dom创建过程
(1). 我们通过 React.createElement 最终创建出来一个 ReactElement对象:
(2). 这个ReactElement对象是什么作用呢?React为什么要创建它呢?
原因是React利用ReactElement对象组成了一个JavaScript的对象树, JavaScript的对象树就是虚拟DOM(Virtual DOM)
(3).如何查看ReactElement的树结构呢?
我们可以将之前的jsx返回结果进行打印; 注意下面代码中我打jsx的打印;
(4). 而ReactElement最终形成的树结构就是Virtual DOM
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>点击变色案例</title> <style> ul>li { margin-bottom: 5px; cursor: pointer; } .red { color: red; } </style> </head> <body> <!-- 根 --> <div id="root"></div> <!-- 引入三个必备库 --> <script src="../lib/react.js"></script> <script src="../lib/react-dom.js"></script> <script src="../lib/babel.js"></script> <script type="text/babel"> // 1. 定义跟组件 class App extends React.Component { // 1.1 构造函数 constructor() { super(); this.state = { msg: '点击变色案例', movies: ["星际穿越", "盗梦空间", "大话西游", "流浪地球"], currentIndex: -1 } } //点击变色方法 myClick(index) { this.setState({ currentIndex: index }) } // 1.2 渲染方法 render() { let { msg, movies, currentIndex } = this.state; //写法1: 全部写在return里 /* { return ( <div> <h2>{msg}</h2> <ul> {movies.map((item, index) => ( <li key={index} onClick={() => this.myClick(index)} className={index == currentIndex ? "red" : ""} >{item}</li> ) )} </ul> </div> ); } */ // 写法2:抽离出来整个li列表 /* { let liElement = movies.map((item, index) => ( <li key={index} onClick={() => this.myClick(index)} className={index == currentIndex ? "red" : ""} >{item}</li> )) return ( <div> <h2>{msg}</h2> <ul> {liElement} </ul> </div> ); } */ // 写法3:将map的回调抽离出来 { let liElementFunc = (item, index) => ( <li key={index} onClick={() => this.myClick(index)} className={index == currentIndex ? "red" : ""} >{item}</li> ); return ( <div> <h2>{msg}</h2> <ul> {movies.map(liElementFunc)} </ul> </div> ); } } } // 2. 创建root并渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")); root.render(<App />) </script> </body> </html>