React基础 - JSX语法
一、认识JSX语法
1.1 认识JSX
-
看一段代码
// 定义跟组件 const element = <div>Hello World</div> // 渲染跟组件 const root = ReactDOM.createRoot(document.querySelector("#root")) root.render(element)
-
element变量的声明右侧赋值的标签语法是什么呢?
-
它不是一段字符串(因为没有使用引号包裹);
-
它看起来是一段HTML元素,但是我们能在js中直接给一个变量赋值html吗?
-
当然不可以,如果我们将
script
标签的type="text/babel"
去除掉,那么就会出现语法错误 -
<div>Hello World</div>
,其实就是一段jsx的语法
-
-
JSX是什么?
-
JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法
-
它用于描述UI界面,并且其完成可以和JavaScript融合在一起使用
-
它不同于Vue中的模块语法,所以不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind)
-
1.2 为什么React选择JSX编写?
-
React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
-
比如UI需要绑定事件(button、a原生等等)
-
比如UI中需要展示数据状态
-
比如在某些状态发生改变时,又需要改变UI
-
-
他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component)
二、JSX的基本使用
2.1 JSX的书写规范
-
JSX的顶层只能有一个根元素,所以很多时候会在外层包裹一个div元素(或者Fragment)
-
为了方便阅读,通常会在jsx的外层包裹一个小括号(),将整个jsx当作一个整体,这样jsx可以进行换行书写
-
JSX中的标签可以是单标签,也可以是双标签
- 注意:如果是单标签,必须以
/>
结尾
- 注意:如果是单标签,必须以
2.2 JSX的注释写法
- 在
.jsx
文件中可以 使用快捷键Ctrl + /
{ /* JSX的注释写法 */ }
{/* 我是注释 */}
2.3 JSX嵌入变量作为子元素
-
当变量是
Number、String、Array
类型时,可以直接显示 -
当变量是
null、undefined、Boolean
类型时,内容为空-
如果希望可以显示null、undefined、Boolean,那么需要转成字符串
-
转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式
-
-
Object对象类型不能作为子元素(not valid as a React child)
const root = ReactDOM.createRoot(document.querySelector("#root")) class App extends React.Component { constructor() { super() this.state = { counter: 100, message: "hello react", names: ["abc", "cba", "nba"], aaa: null, bbb: undefined, ccc: true, friend: { name: "星儿" } } } render() { const { counter, message, names } = this.state const { aaa, bbb, ccc } = this.state const { friend } = this.state return ( <div> {/* Number/String/Array 直接显示出来 */} <h2>{counter}</h2> <h2>{message}</h2> <h2>{names}</h2> {/* Null/Undefined/Boolean 需要转换成字符串才能正确显示 */} <h2>{String(aaa)}</h2> <h2>{bbb + ""}</h2> <h2>{ccc.toString()}</h2> {/* 3.Object类型不能作为子元素进行显示 */} <h2>{friend.name}</h2> <h2>{Object.keys(friend)[0]}</h2> </div> ) } } root.render(<App/>)
2.4 插入表达式
- 运算表达式
- 三元运算符
- 执行一个函数
// 1.定义App根组件
class App extends React.Component {
constructor() {
super()
this.state = {
firstName: "kobe",
lastName: "bryant",
age: 20,
movies: ["流浪地球", "星际穿越", "独行月球"]
}
}
render() {
const { firstName, lastName } = this.state
const fullName = firstName + " " + lastName
const { age } = this.state
const ageText = age >= 18 ? "成年人": "未成年人"
const liEls = this.state.movies.map(movie => <li>{movie}</li>)
return (
<div>
<h2>{firstName + " " + lastName}</h2>
<h2>{fullName}</h2>
<h2>{ageText}</h2>
<h2>{age > 18 ? "成年人" : "未成年人"}</h2>
<ul>
{liEls}
</ul>
<ul>{this.state.movies.map(movie => <li>{movie}</li>)}</ul>
<ul>{this.getMovieEls()}</ul>
</div>
)
}
// 在逻辑比较复杂的时候,推荐将其写入方法中
getMovieEls() {
return this.state.movies.map(movie => <li>{movie}</li>)
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App/>)
2.5 JSX绑定属性
-
基本属性
-
class属性
- 在JSX中使用className替代class
- 绑定写法
- 字符串拼接
- 将所有的 class 放入数组中
- 使用第三方库,classNames(后续补充)
-
style属性
- 绑定对象类型
- 注意:此处并非上述的插入变量作为子元素,所以能写入对象
// 1.定义App根组件 class App extends React.Component { constructor() { super() this.state = { title: "我是标题", imgUrl: "https://img1.baidu.com/it/u=3545225878,2086373205&fm=253&fmt=auto&app=138&f=JPEG?w=450&h=236", href: "https://www.baidu.com", isActive: true, objStyle: { color: "red", fontSize: "30px" } } } render() { const { title, imgUrl, href, isActive, objStyle } = this.state const classList = ["abc cba"] if(isActive) classList.push("active") return ( <div> {/* 基本属性 */} <h2 title={title}>我是标题</h2> <img src={imgUrl} /> <a href={href}>百度一下</a> {/* 绑定class:最好使用className */} <h2 className={`abc cba ${isActive ? 'active' : ""}`}>哈哈哈哈</h2> <h2 className={classList.join()}>哈哈哈哈</h2> {/* 绑定style:绑定对象类型 */} <h2 style={{color: "red", fontSize: "30px"}}>呵呵呵呵</h2> <h2 style={objStyle}>呵呵呵呵</h2> </div> ) } } // 2.创建root并且渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")) root.render(<App/>)
- 效果
三、React中的JSX
3.1 事件绑定
-
原生DOM监听事件
-
获取DOM原生,添加监听事件
-
在HTML原生中,直接绑定onclick
-
-
在JSX中的事件监听
-
命名采用小驼峰式(camelCase),而不是纯小写
-
需要通过
{}
传入一个事件处理函数,这个函数会在事件发生时被执行
-
-
this的问题
-
不知道
this
指向,可参考我的另一篇文章一文搞懂JavaScript中this指向 -
在 React 类组件中,绑定事件方法中
this
指向undefined
,为什么这样呢?- btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数
- 类似下面的代码,在执行的时候是拿到这个函数,独立调用,此时
this
指向window
,严格模式下,指向undefined
const obj = { name: "obj", foo: function() { console.log("foo:", this) } } // obj.foo() const config = { onClick: obj.foo } const click = config.onClick click()
-
如何解决this的问题呢?
- bind给btnClick显示绑定this
- 使用 ES6 class fields 语法
- 事件监听时传入箭头函数(推荐)
// 1.定义App根组件 class App extends React.Component { // class fields name = "App" constructor() { super() this.state = { message: "Hello World", counter: 100 } this.btn1Click = this.btn1Click.bind(this) } btn1Click() { console.log("btn1Click", this); this.setState({ counter: this.state.counter + 1 }) } btn2Click = () => { console.log("btn2Click", this) this.setState({ counter: 1000 }) } btn3Click() { console.log("btn3Click", this); this.setState({ counter: 9999 }) } render() { const { message } = this.state return ( <div> {/* 1. bind绑定 */} <button onClick={this.btn1Click}>按钮1</button> {/* 2. ES6 class fields */} <button onClick={this.btn2Click}>按钮2</button> {/* 3. 传入一个箭头函数(推荐) */} <button onClick={() => console.log("btn3Click")}>按钮3</button> <button onClick={() => this.btn3Click()}>按钮3</button> <h2>当前计数: {this.state.counter}</h2> </div> ) } } // 2.创建root并且渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")) root.render(<App/>)
-
-
事件参数传递
-
获取event对象
- 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
- 默认情况下,event对象有被直接传入,函数就可以获取到event对象
-
获取更多参数
- 有更多参数时,最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数
// 1.定义App根组件 class App extends React.Component { constructor() { super() this.state = { counter: 100 } } btnClick(event, name, age) { console.log("btn1", event); console.log("name, age: ", name, age); this.setState({ counter: 999 }) } render() { const { counter } = this.state return ( <div> <h2>{counter}</h2> {/* event参数的传递 */} <button onClick={this.btnClick.bind(this)}>按钮1</button> <button onClick={(event) => this.btnClick(event)}>按钮2</button> {/* 额外参数的传递 */} <button onClick={this.btnClick.bind(this, "马蓝星", "28")}>按钮3(不推荐)</button> <button onClick={(e) => this.btnClick(e, "独孤月", "30")}>按钮4</button> </div> ) } } // 2.创建root并且渲染App组件 const root = ReactDOM.createRoot(document.querySelector("#root")) root.render(<App/>)
-
3.2 条件渲染
-
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容
-
在vue中,我们会通过指令来控制:比如v-if、v-show
-
在React中,所有的条件判断都和普通的JavaScript代码一致
-
-
常见的条件渲染的方式有哪些呢?
-
条件判断语句:适合逻辑较多的情况
-
三元运算符:适合逻辑比较简单
-
与运算符
&&
:适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染 -
v-show的效果:主要是控制display属性是否为none
-
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
isReady: false,
friend: undefined,
isShow: true
};
}
changeShow() {
this.setState({ isShow: !this.state.isShow })
}
render() {
const { isReady, friend, isShow } = this.state;
// 1.条件判断方式一: 使用if进行条件判断
let showElement = null;
if (isReady) {
showElement = <h2>我已经准备好了</h2>;
} else {
showElement = <h1>还在准备中ing</h1>;
}
return (
<div>
{/* 1.方式一: 根据条件给变量赋值不同的内容 */}
<div>{showElement}</div>
{/* 2.方式二: 三元运算符 */}
<div>
{isReady ? <button>冲呀!</button> : <h3>战斗警报!</h3>}
</div>
{/* 3.方式三: &&逻辑与运算 */}
{/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */}
<div>
{friend && <div>{friend.name + " " + friend.desc}</div>}
</div>
{/* 4.方式四: 模拟 v-show */}
<div>
<button onClick={() => this.changeShow()}>切换</button>
<h2 style={{display : isShow ? "block" : "none"}}>我是通过 display 属性控制的</h2>
</div>
</div>
);
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
3.3 列表渲染
-
在React中并没有像 Vue 模块语法中的v-for指令,而且需要通过JavaScript代码的方式组织数据,转成JSX:
- React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活
-
如何展示列表呢?
- 在React中,展示列表最多的方式就是使用数组的map高阶函数
-
很多时候在展示一个数组中的数据之前,可能需要先对它进行一些处理:
- 过滤掉一些内容:filter函数
- 截取数组中的一部分内容:slice函数
-
列表中的key
- 列表渲染如果不绑定 key ,react会抛出一个警告
- key主要的作用是为了提高diff算法时的效率
-
案例
<!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>Document</title>
<style>
.item {
margin: 10px;
padding: 0 20px;
border: 1px solid orange;
}
</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">
class App extends React.Component {
constructor() {
super()
this.state = {
students: [
{ id: 111, name: "独孤月", score: 88 },
{ id: 112, name: "马蓝星", score: 97 },
{ id: 113, name: "朱皮特", score: 82 },
{ id: 114, name: "葫芦丝儿", score: 66 },
]
}
}
render() {
const { students } = this.state
// 分数大于80的学生进行展示
const filterStudents = students.filter(item => {
return item.score > 80
})
// 取两名学生
// slice(start, end): [start, end)
const sliceStudents = filterStudents.slice(0, 2)
return (
<div>
<h2>学生列表数据</h2>
<div className="list">
{
students.filter(item => item.score > 80).slice(0, 2).map(item => {
return (
<div className="item" key={item.id}>
<h2>学号: {item.id}</h2>
<h3>姓名: {item.name}</h3>
<h1>分数: {item.score}</h1>
</div>
)
})
}
</div>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App/>)
</script>
</body>
</html>
四、JSX的本质和原理
4.1 JSX的本质
-
jsx 仅仅只是
React.createElement(component, props, ...children)
函数的语法糖- 所有的 jsx 最终都会被转换成React.createElement的函数调用
-
createElement需要传递三个参数:
-
参数一:type
- 当前ReactElement的类型
- 如果是标签元素,那么就使用字符串表示,比如 “div”
- 如果是组件元素,那么就直接使用组件的名称
-
参数二:config
- 所有jsx中的属性都在config中以对象的属性和值的形式存储
- 比如传入className作为元素的class
-
参数三:children
- 存放在标签中的内容,以children数组的方式进行存储
-
4.2 JSX的原理
- jsx的代码
<div> <div className="header">Header</div> <div className="content"> <div>Banner</div> <ul> <li>列表数据1</li> <li>列表数据2</li> <li>列表数据3</li> <li>列表数据4</li> <li>列表数据5</li> </ul> </div> <div className="footer">Footer</div> </div>
- 使用babel进行转换
- Babel官网
- 将jsx代码放入 Babel 官网进行转换
- 转换后的代码就可以不引入Babel依赖,
type="text/babel"
也可以不写
<!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>Document</title>
</head>
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib/react-dom.js"></script>
<script>
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
};
}
render() {
const { message } = this.state;
const element = React.createElement(
"div",
null,
/*#__PURE__*/ React.createElement(
"div",
{
className: "header",
},
"Header"
),
/*#__PURE__*/ React.createElement(
"div",
{
className: "content",
},
/*#__PURE__*/ React.createElement("div", null, message),
/*#__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"
)
);
return element;
}
}
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(React.createElement(App, null));
</script>
</body>
</html>
4.3 JSX -> 虚拟DOM -> 真实DOM
- 在控制台打印下,看一下上面 render 函数中的
element
究竟是什么?- 通过 React.createElement 最终创建出来一个 ReactElement对象
- 这个 JavaScript 对象树就是虚拟DOM
- jsx代码通过 babel 转换成 ReactElement对象 通过渲染 变成真实 DOM
4.4 声明式编程
-
虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
-
React官方的说法:Virtual DOM 是一种编程理念
-
在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
-
我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation)
-
-
这种编程的方式赋予了React声明式的API:
- 只需要告诉React希望让UI是什么状态
- React来确保DOM和这些状态是匹配的
- 不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来
五、案例练习
5.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>Document</title>
<style>
.active {
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.定义App根组件
class App extends React.Component {
constructor() {
super()
this.state = {
movies: ["火星救援", "飞驰人生", "独行月球", "流浪地球"],
curIndex: 0
}
}
itemClick(index) {
this.setState({
curIndex: index
})
}
render() {
const { movies, curIndex } = this.state
return (
<div>
<ul>
{movies.map((movie, index) => {
return (
<li
key={movie}
className={curIndex === index ? 'active' : ''}
onClick={() => this.itemClick(index)}
>
{movie}
</li>
)
})}
</ul>
</div>
)
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App/>)
</script>
</body>
</html>
- 重构一:将对
<li>
的处理抽离render() { const { movies, curIndex } = this.state const liEls = movies.map((movie, index) => { return ( <li key={movie} className={curIndex === index ? 'active' : ''} onClick={() => this.itemClick(index)} > {movie} </li> ) }) return ( <div> <ul>{liEls}</ul> </div> ) }
- 重构二:对 map函数处理的 抽离
render() { const { movies, curIndex } = this.state const movieHandle = ((movie, index) => { return ( <li key={movie} className={curIndex === index ? 'active' : ''} onClick={() => this.itemClick(index)} > {movie} </li> ) }) return ( <div> <ul>{movies.map(movieHandle)}</ul> </div> ) }
5.2 购物车
- 效果
- 数据
// data.js const books = [ { id: 1, name: '《算法导论》', date: '2006-9', price: 85.00, count: 1 }, { id: 2, name: '《UNIX编程艺术》', date: '2006-2', price: 59.00, count: 1 }, { id: 3, name: '《编程珠玑》', date: '2008-10', price: 39.00, count: 1 }, { id: 4, name: '《代码大全》', date: '2006-3', price: 128.00, count: 1 }, ]
- 价格格式化
// format.js function formatPrice(price) { return "¥" + Number(price).toFixed(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>Document</title> <style> table { border-collapse: collapse; text-align: center; } thead { background-color: #f2f2f2; } td, th { padding: 10px 16px; border: 1px solid #aaa; } </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 src="./format.js"></script> <script type="text/babel"> class App extends React.Component { constructor() { super() this.state = { books: books } } // 加 increment(index) { // 注意:此处不能直接修改state中的数据 const newBooks = [...this.state.books] newBooks[index].count += 1 this.setState({ books: newBooks }) } // 减 decrement(index) { // 注意:此处不能直接修改state中的数据 const newBooks = [...this.state.books] newBooks[index].count -= 1 this.setState({ books: newBooks }) } // 移除某一行 removeBook(index) { const newBooks = [...this.state.books] newBooks.splice(index, 1) this.setState({ books: newBooks }) } // 总价格 getTotalPrice() { return this.state.books.reduce((prev, item) => { return prev + item.price * item.count }, 0) } render() { const { books } = this.state let showElement = null if(books.length) { showElement = ( <div> <table> <thead> <tr> <td>序号</td> <td>书籍名称</td> <td>出版日期</td> <td>价格</td> <td>购买数量</td> <td>操作</td> </tr> </thead> <tbody> {books.map((book, index) => { return ( <tr key={book.id}> <td>{index + 1}</td> <td>{book.name}</td> <td>{book.date}</td> <td>{formatPrice(book.price)}</td> <td> <button disabled={book.count <= 1} onClick={() => this.decrement(index)}>-</button> {book.count} <button onClick={() => this.increment(index)}>+</button> </td> <td> <button onClick={() => this.removeBook(index)}>移除</button> </td> </tr> ) })} </tbody> </table> <h2>总价格:{formatPrice(this.getTotalPrice())}</h2> </div> ) } else { showElement = ( <div> <h2>购物车为空,请添加书籍~</h2> </div> ) } return showElement } } const root = ReactDOM.createRoot(document.querySelector("#root")) root.render(<App/>) </script> </body> </html>
- 代码优化
- 优化控制数量的方法
- 将书籍列表和空数据的jsx抽离为函数
class App extends React.Component { constructor() { super() this.state = { books: books } } // 数量改变 changeCount(index, count=1) { // 注意:此处不能直接修改state中的数据 const newBooks = [...this.state.books] newBooks[index].count += count this.setState({ books: newBooks }) } // 移除某一行 removeBook(index) { const newBooks = [...this.state.books] newBooks.splice(index, 1) this.setState({ books: newBooks }) } // 总价格 getTotalPrice() { return this.state.books.reduce((prev, item) => { return prev + item.price * item.count }, 0) } // 渲染书籍列表 renderBookList() { const { books } = this.state return ( <div> <table> <thead> <tr> <td>序号</td> <td>书籍名称</td> <td>出版日期</td> <td>价格</td> <td>购买数量</td> <td>操作</td> </tr> </thead> <tbody> {books.map((book, index) => { return ( <tr key={book.id}> <td>{index + 1}</td> <td>{book.name}</td> <td>{book.date}</td> <td>{formatPrice(book.price)}</td> <td> <button disabled={book.count <= 1} onClick={() => this.changeCount(index, -1)}>-</button> {book.count} <button onClick={() => this.changeCount(index)}>+</button> </td> <td> <button onClick={() => this.removeBook(index)}>移除</button> </td> </tr> ) })} </tbody> </table> <h2>总价格:{formatPrice(this.getTotalPrice())}</h2> </div> ) } // 渲染空数据 renderBookEmpty() { return <div><h2>购物车为空, 请添加书籍~</h2></div> } render() { const { books } = this.state return books.length ? this.renderBookList() : this.renderBookEmpty() } } const root = ReactDOM.createRoot(document.querySelector("#root")) root.render(<App/>)
转载自:https://juejin.cn/post/7142366426299367460