一、认识JSX语法
认识JSX
// 1.定义根组件
const element = <div>Hello World</div>
// 2.渲染根组件
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(element)
- element变量声明赋值的标签语法是什么?
- 不是一段字符串(无引号包裹)
- 像HTML元素,但却写在了 js 中
- 去掉 script 标签的 type="text/babel",会报错
- 是jsx语法
- JSX是什么?
- 是一种JavaScript的语法扩展(eXtension),也称之为JavaScript XML,看起来像一段XML语法
- 用于描述UI界面,可以于 JS 融合使用
- 不同于Vue中的模块语法,无需专门学习模块语法中的指令(v-if、v-bind等)
为什么React选择了JSX
- react认为渲染逻辑本质上与其他UI逻辑存在内在耦合
- 如UI需要绑定事件(button、a链接等)
- 如UI中需要展示数据状态
- 如在某些状态发生改变时,又需要改变UI
- 它们之间密不可分,所以React没有将 标记(html、css) 分离到不同的文件中,而是将其组合到了一起,是为组件(Component)
- JSX其实是嵌入到 JS 中的一种结构语法
JSX的书写规范
- JSX的顶层只能有一个根元素,很多时候会在外层包裹一层div元素(或Fragment)
- 为了方便阅读,会在 jsx 外层包裹一个小括号(),并且 jsx 可以进行换行书写
- JSX 中的标签可以是单标签,也可以是双标签
- 如果是单标签,必须以 /> 结尾
二、JSX的基本使用
JSX的使用
- jsx中的注释
- JSX嵌入变量作为子元素
- 情况一:当变量是Number、String、Array类型时,可以直接显示
- 数组会自动拼接成字符串
- 情况二:当变量是null、undefined、Boolean类型时,内容为空
- 如果希望显示null、undefined、Boolean,可以转成字符串
- toString方法、和空字符串拼接、String(变量)等方式
- 情况三:Object对象类型不能作为子元素(not valid as a React child)
- 情况一:当变量是Number、String、Array类型时,可以直接显示
- JSX嵌入表达式
- 运算表达式
- 三元运算符
- 执行一个函数
- JSX绑定属性
- 如元素都会有title属性
- 如img元素会有src属性
- 如a元素会有href属性
- 如元素可能需要绑定class(使用 className)
- 如原生使用内联样式style
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector('#root'))
class App extends React.Component {
constructor() {
super()
this.state = {
msg: 'Hello World',
isActive: false,
bdHref: 'https://www.baidu.com',
arr: ['a', 'b', 'c']
}
}
render() {
const {msg, isActive, bdHref, arr} = this.state
const className = ['aaa', 'bbb']
if (!isActive) {
className.push('active')
}
return (
<div className={className.join(' ')} style={{backgroundColor: 'skyblue'}}>
{/* 注释 */}
<h2>{isActive ? 'active' : 'not active'}</h2>
<h2>{this.func()}</h2>
<a href={bdHref}>百度一下</a>
<h2>{arr}</h2>
</div>
)
}
func() {
return this.state.msg
}
}
root.render(<App />)
</script>
三、JSX的事件绑定
React事件绑定
- 给原生DOM添加一个监听事件
- 1⃣️、获取DOM原生,添加监听事件
- 2⃣️、在HTML原生中,直接绑定 onclick
- 在React中实现事件监听
- React事件的命名采用小驼峰(camelCase),而不是纯小写
- 需要通过 {} 传入一个事件处理函数,这个函数会在事件发生时被执行
this的四种绑定规则
- 1.默认绑定:独立执行 foo()
- 2.隐式绑定:被一个对象执行 obj.foo() -> obj
- 3.显式绑定:call/apply/bind foo.call('str') -> String('str')
- 4.new绑定:new Foo() -> 创建一个新对象,并且赋值给this
this的绑定问题
- 事件执行之后,可能需要获取当前类的对象中相关的属性,需要用到this
- 在绑定的事件函数中打印this,发现时undefined
const obj = {
name: 'obj',
foo: function() {
console.log('foo', this)
}
}
// obj.foo()
const config = {
onClick: obj.foo.bind(obj) // 这样显示绑定,click调用的时候,this指向obj
}
const click = config.onClick
click()
- 为什么是undefined?
- btnClcik函数不是我们主动调用的,当button发生点击时,React内部调用了btnClick
- 它内部调用时,并不知道如何绑定正确的this
- 解决this问题
- 方案一:bind给btnClick显示绑定this
- 方案二:使用ES6 class fields 语法
- 方案三:事件监听时箭头函数(推荐)
class App extends React.Component {
constructor() {
super()
this.state = {
count: 0
}
this.btn1Click = this.btn1Click.bind(this)
}
btn1Click() {
console.log('btn1', this);
}
// 箭头函数无this
btn2Click = () => {
console.log('btn2', this);
}
btn3Click = () => {
console.log('btn3', this);
}
render() {
const {msg, count} = this.state
return (
<div>
<h2>当前计数:{count}</h2>
{/* 1.bind绑定this */}
<button onClick={this.btn1Click}>按钮1</button>
{/* 2.ES6 class fields */}
<button onClick={this.btn2Click}>按钮2</button>
{/* 2.直接传入一个箭头函数(重要) */}
<button onClick={() => this.btn3Click()}>按钮3</button>
</div>
)
}
}
事件传参传递
- 执行事件函数时,需要获取一些参数信息,如event对象,其他参数
- 情况一:event对象
- 用来阻止冒泡、默认行为
- 默认情况下,event对象直接传入,函数即可获取event对象
- 情况二:获取更多参数
- 最佳实践:传入一个箭头函数,主动执行的事件函数,并且传入相关的其它参数
class App extends React.Component {
constructor() {
super()
this.state = {
msg: 'Hello World'
}
}
btnClick(e, name, age) {
console.log('event', e);
console.log('this', this);
console.log('name', name);
console.log('age', age);
}
render() {
const { msg } = this.state
return (
<div>
{/* 1.event参数的传递 */}
<button onClick={this.btnClick.bind(this)}>按钮1</button>
<button onClick={(e) => this.btnClick(e)}>按钮2</button>
{/* 2.额外的参数的传递 */}
{/* 第一种参数顺序改变注意 */}
<button onClick={this.btnClick.bind(this, '你好', 18)}>按钮3(不推荐)</button>
{/* 推荐这种 */}
<button onClick={(e) => this.btnClick(e, '你好', 18)}>按钮4</button>
</div>
)
}
}
列表渲染案例
class App extends React.Component {
constructor() {
super()
this.state = {
books: ['遮天', '斗破苍穹', '完美世界', '诛仙'],
currentIndex: 0
}
}
btnClick(index) {
this.setState({
currentIndex: index
})
}
render() {
const {currentIndex, books} = this.state
return (
<div>
<ul>
{books.map((i, n) => {
return (
<li
className={currentIndex === n ? 'active' : ''}
key={i}
onClick={() => {
this.btnClick(n)
}}
>
{i}
</li>
)
})}
</ul>
</div>
)
}
}
四、JSX的条件渲染
React条件渲染
- 界面的内容会根据不同的情况展示不同的内容,可决定是否渲染某部分内容
- vue中,通过指令控制(v-if、v-show)
- react中,所以的条件判断都和普通的JavaScript代码一致
- 常见条件渲染方式
- 1⃣️、条件判断语句
- 适合逻辑较多的情况(if...else...)
- 2⃣️、三元运算符
- 适合逻辑比较简单
- 3⃣️、与运算符 &&
- 适合如果条件成立,渲染某个组件;如果条件不成立,什么内容都不渲染
- 4⃣️、v-show的效果
- 主要是控制display属性是否为none
- 1⃣️、条件判断语句
条件渲染案例
class App extends React.Component {
constructor() {
super()
this.state = {
msg: 'Hello React',
isShow: false
}
}
btnClick() {
this.setState({
isShow: !this.state.isShow
})
}
render() {
const { msg, isShow } = this.state
return (
<div>
<button onClick={() => this.btnClick()}>切换显示</button>
{isShow && <h2>{msg}</h2>}
<h2 style={{ display: isShow ? 'block' : 'none' }}>{msg}</h2>
</div>
)
}
}
五、JSX的列表渲染
React列表渲染
- 真是开发中从服务器请求大量的数据,数据会以列表的形式存储
- 如歌曲、歌手、排行榜列表的数据
- 如商品、购物车、评论列表的数据
- 如好友消息、动态、联系人列表的数据
- 在React中并没有像Vue模版语法中的v-for指令,需要我们通过JavaScript代码的方式组织数据,转成JSX
- 如何展示列表
- 在React中,展示列表最多的方式就是使用数组的map高阶函数
- 很多时候展示一个数组中的数据之前,需要对它进行一些处理
- 过滤一些内容:filter
- 截取数组中的一部分:slice
列表中的key
- 不使用key会报警告
- Each child in a list should have a unique "key" prop
- 警告告诉我们需要在列表展示的jsx添加一个key
- key主要作用为了提高diff算法时的效率
class App extends React.Component {
constructor () {
super()
this.state = {
books: [
{bookName: '遮天', author: '辰东', id: 1},
{bookName: '斗破苍穹', author: '天蚕土豆', id: 2},
{bookName: '完美世界', author: '辰东', id: 3},
{bookName: '大主宰', author: '天蚕土豆', id: 4},
{bookName: '圣墟', author: '辰东', id: 5},
]
}
}
render () {
const { books } = this.state
return (
<div>
<h2>图书列表</h2>
<ul>
{books.filter(i => i.id <= 4).slice(0, 4).map(i => {
return (
<li key={i.id}>{i.bookName}</li>
)
})}
</ul>
</div>
)
}
}
六、JSX的原理和本质
JSX的本质
- jsx仅仅只是 React.createElement(Component, props, ...children)函数的语法糖
- 所有的jsx最终都会被转换成 React.createElement 的函数调用
- createElement需要传递三个参数
- 参数一:type
- 当前 ReactElement 的类型
- 如果是标签元素,那么就使用字符串表示 "div"
- 如果是组件元素,那么就直接使用组件的名称
- 参数二:config
- 所有jsx中的属性都在config中以对象的属性和值的形式存储
- 比如传入className作为元素的class
- 参数三:children
- 存放在标签中的内容,以children数组的方式进行存储
- 如果是多个元素,React内部对它们进行处理
- 参数一:type
createElement 源码
- package/react/src/ReactElements.js/createElement
Babel 官网查看
- 默认jsx是通过babel帮我们进行语法转换的,直接写jsx代码都需要依赖babel
- babel官网中快速查看转换过程
直接编写jsx代码
- 自己编写React.createElement代码
- 界面依旧正常渲染
- 不需要引入babel
- type="text/babel"可以删除
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script>
class App extends React.Component {
constructor () {
super()
this.state = {
}
}
render() {
const ele = /*#__PURE__*/React.createElement("div", {
class: "container",
title: "Hello",
children: [/*#__PURE__*/React.createElement("h2", {
children: "Hello World"
}), /*#__PURE__*/React.createElement("div", {
class: "content",
children: /*#__PURE__*/React.createElement("ul", {
children: [/*#__PURE__*/React.createElement("li", {
children: "1"
}), /*#__PURE__*/React.createElement("li", {
children: "2"
}), /*#__PURE__*/React.createElement("li", {
children: "3"
})]
})
})]
});
return ele
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(React.createElement(App))
</script>
虚拟DOM的创建过程
- 通过React.createElement最终创建出来一个 ReactElement 对象
- React.Element对象作用?为什么需要?
- React利用React.Element对象组成了一个JavaScript对象树
- JavaScript对象树就是虚拟DOM(Virtual DOM)
- 如何查看React.Element的树结构?
- 在jsx返回结果进行打印
- ReactElement最终形成的树结构就是Virtual DOM
jsx - 虚拟DOM - 真实DOM
jsx代码 => ReactElement对象 => 真实DOM
声明式编程
- 虚拟DOM帮助我们从命令式编程转到了声明式编程
- React官方说法:Virtual DOM 是一种编程理念
- 理念中,UI以一种理想化或者说是虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
- 可以通过ReactDOM.render让虚拟DOM和真实DOM同步起来,这个过程称为 协调(Reconciliation)
- 这种编程方式赋予了React声明式的API
- 你只需要告诉React希望让UI是什么状态
- React来确保DOM和这些状态是匹配的
- 无需直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来
购物车案例
- 在界面上以表格的形式,显示一些书籍的数据
- 在底部显示书籍的总价格
- 点击 + 或 - 可以增加或减少书籍数量(如果为1,那么不能继续)
- 点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空)
| 书籍名称 | 出版日期 | 价格 | 购买数量 | 操作 | |
|---|---|---|---|---|---|
| 1 | 《算法导论》 | 2006-9 | ¥85.00 | -1+ | 移除 |
| 2 | 《UNIX编程艺术》 | 2006-2 | ¥59.00 | -1+ | 移除 |
| 3 | 《编程珠玑》 | 2008-10 | ¥39.00 | -1+ | 移除 |
| 4 | 《代码大全》 | 2006-3 | ¥128.00 | -1+ | 移除 |
总价格:¥311
书籍数据
const books = [
{
bookName: '《算法导论》',
public: '2006-9',
price: '49.00',
count: 1,
id: 1
},
{
bookName: '《UNIX编程艺术》',
public: '2006-2',
price: '59.00',
count: 1,
id: 2
},
{
bookName: '《编程珠玑》',
public: '2008-10',
price: '39.00',
count: 1,
id: 3
},
{
bookName: '《代码大全》',
public: '2006-3',
price: '99.00',
count: 1,
id: 4
},
]
代码
class App extends React.Component {
constructor() {
super()
this.state = {
books: books
}
}
increment(index) {
const { books } = this.state
const newBooks = [...books]
newBooks[index].count++
this.setState({
books: newBooks
})
}
decrement(index) {
const { books } = this.state
const newBooks = [...books]
newBooks[index].count--
this.setState({
books: newBooks
})
}
removeItem(index) {
const { books } = this.state
const newBooks = [...books]
newBooks.splice(index, 1)
this.setState({
books: newBooks
})
}
render() {
const { books } = this.state
const totalPrice = books.reduce((pre, item) => {
return pre + item.price * item.count
}, 0)
return (
<div>
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
books.map((item, index) => {
return (
<tr key={item.id}>
<td>{index + 1}</td>
<td>{item.bookName}</td>
<td>{item.public}</td>
<td>{item.price}</td>
<td>
<button
disabled={item.count <= 0}
onClick={() => this.decrement(index)}
>
-
</button>
{item.count}
<button onClick={() => this.increment(index)}>+</button>
</td>
<td>
<button onClick={() => this.removeItem(index)}>
移除
</button>
</td>
</tr>
)
})
}
</tbody>
</table>
<div>总价格:{'¥' + totalPrice}</div>
{books.length === 0 && <h1>请选择您的书籍</h1>}
</div>
)
}
}