《二》React 中的 JSX 语法

JSX 全称是 JavaScriptXML,是一种类似于 XML 的 JavaScript 扩展语法。它允许 HTML 与 JavaScript 的混写,HTML 可以直接写在 JavaScript 之中。

遇到 HTML 标签(以 < 开头),就用 HTML 规则解析:若小写字母开头,则将标签转为 HTML 中的同名元素,若 HTML 中无该标签对应的同名元素,则报错;若大写字母开头, React 就去渲染对应的组件,若组件没有定义,则报错。 遇到代码块(以 { 开头),就用 JavaScript 规则解析。

XML 早期用于存储和传输数据,后来逐渐被 JSON 代替了。

// 使用 JSX 语法定义一个 React 元素
const element = <h1>Hello,React</h1>

// 渲染 React 元素到页面中
<div id="root"></div>
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(element)

为什么 React 会选择 JSX 这种语法?

React 认为渲染逻辑与 UI 逻辑存在很强的内在耦合。比如:在 UI 中需要展示数据状态、绑定事件;当某些数据状态改变时,需要改变 UI。因此 React 决定实现一种 HTML in JS 的模式,就是 JSX。

JSX 的本质:

JSX 本质上仅仅是 React.createElement(type, config, children) 的语法糖,所有的 JSX 最终都会被转换成 React.createElement() 函数的调用。JSX 就是用来简化创建虚拟 DOM 的写法的。

React.createElement() 的三个参数:

  1. type:当前 ReactElement 的类型。如果是 HTML 元素,就使用标签名;如果是 React 组件,就使用组件的名称。
  2. config:所有属性和属性值,以对象的形式存储。
  3. children:标签中的内容,以数组的形式存储。

JSX 的转换过程:

浏览器不识别 JSX 语法,可以使用 Babel 将 JSX 转换为浏览器可识别的 JS。Babel 每遇到一个标签就会将其转换成 React.createElement()

JSX 的语法:

JSX 中的标签可以是单标签,也可以是双标签。如果是单标签的话,必须以 /> 结尾。
为了方便阅读,通常在 JSX 的外层包裹一个小括号。

注释:

JSX 中的注释需要写在花括号中。

const element = <h1>{/* 注释 */}Hello,React</h1>,

JSX 中只能有一个根元素:

JSX 中只能有一个根元素,可以在根元素里包含多个子元素。

// 错误
const element = (
	<h1>Hello,React</h1>
	<h1>Hi,JavaScript</h1>
)

//正确
const element = (
	<div>
		<h1>Hello,React</h1>
		<h1>Hi,JavaScript</h1>
	</div>
)

JSX 也是一个表达式:

JSX 也是一个表达式,在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。也就是说,可以在 if 语句和 for 循环的代码块中使用 JSX、将 JSX 赋值给变量、把 JSX 当作参数传入以及从函数中返回 JSX。

function getGreeting(name) {
	 if (user) {
	    return <h1>Hello, {name}!</h1>
	 }
}

给 JSX 中的元素嵌入内容和绑定属性:

给 JSX 中的元素嵌入内容:

dangerouslySetInnerHTML 是 React 中 innerHTML 的替代品。可以通过 dangerouslySetInnerHTML 在 JSX 中实现换行。

<div dangerouslySetInnerHTML={{__html: '你好<br />世界'}}></div>
  1. 当嵌入的内容是变量:
    • 当嵌入的内容是 String、Number、Array 类型的变量时,直接显示。
    • 当嵌入的内容是 Boolean、Null、Undefined 类型的变量时,显示为空。
    • 当嵌入的内容是 Object 类型的变量时,会报错,Object 类型的变量不能作为内容嵌入。
    const types = ['React', 'Vue', 'Angular']
    const element = (
    	<div>
    		<h1>{types}</h1>
    	</div>
    )
    
  2. 当嵌入的内容是表达式:在 JSX 语法中,可以在大括号内放置任何有效的 JavaScript 表达式。

    在 JSX 中可以嵌入 JS 表达式,一个表达式会产生一个值。
    但是不能嵌入 JS 语句:不能使用 if elseswitch case 语句,可以使用三元运算符来代替;不能使用 for 循环语句,可以使用 map() 等方法来代替。

    const age = 20
    const persons = [
    	{
    		id: 1,
    		name: 'Lee',
    	},
    	{
    		id: 2,
    		name: 'Mary',
    	},
    ]
    const getMessage = () => {
    	return 'Hello,React'
    }
    
    const element = (
    	<div>
    		<h1>{10 + 20}</h1>
    		<h1>{age > 18 ? '成年人' : '未成年人'}</h1>
    		<h1>
    			{persons.map(item => (<div key={item.id}>{item.name}</div>))}
    		</h1>
    		<h1>{getMessage()}</h1>
    	</div>
    )
    
给 JSX 中的元素绑定属性:

可以使用 JavaScript 表达式作为 JSX 中元素的属性值,需要包裹在 {} 中。

因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 属性名要使用小驼峰命名。例如:JSX 里的 class 变成了 className。

小驼峰:首字母小写,后面每个单词的首字母都大写。
大驼峰:首字母大写,后面每个单词的首字母也都大写。

const imgUrl = 'https://img1.baidu.com/it/u=914516326,2749589618&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto'
const element = <img src={imgUrl} />

样式:

  1. 使用内联样式:style 接收一个对象 style={{key: value, key: value}} ,属性名要使用小驼峰形式。
    // 静态内联样式
    <div style={{color: 'red', fontSize: 12}}></div>
    	
    // 动态内联样式
    <div style={{color: `${this.state.isActive ? 'red' : ''}`, fontSize: 12}}></div>
    
  2. 使用 class 类选择器:
    // 静态 class
    <div className="font12 colorRed"></div>
    		 
    // 动态 class
    <div className={this.state.isActive ? 'colorRed' : ''}></div>
    		 
    // 静态 class 和动态 class 同时使用
    <div className={`font12 ${this.state.isActive ? 'colorRed' : ''}`}></div>
    

条件渲染和列表渲染:

条件渲染:

React 中可以使用 if 条件语句、三元运算符或者逻辑与运算符,根据不同的状态渲染不同的内容。

  1. 使用 if 条件语句。
    const isLogin = true
    
    // 使用 if 条件语句,根据不同的条件给变量赋不同的值值
    let element
    if (isLogin) {
    	element = <h1>已登录</h1>
    } else {
    	element = <h1>未登录</h1>
    }
    
  2. 使用三元运算符。
    const isLogin = true
    
    const element = <div>{isLogin ? '已登录' : '未登录'}</div>
    
  3. 使用逻辑与运算符:如果条件成立,渲染某个内容;如果条件不成立,什么内容也不渲染。

    如果第一个操作数不是 boolean 类型,而是数值 0 时,会在 UI 中显示 0。因此最好使用 !! 转化一下。

    const isLogin = true
    const element = isLogin && <div>已登录</div>
    
列表渲染:

React 中进行列表渲染最常见的方式就是使用数组的 map() 方法。

使用 map() 方法遍历得到的元素需要设置 key 属性。key 属性在兄弟节点之间应该是独一无二的,但是它们不需要全局唯一。

key 属性是虚拟 DOM 对象的标识,在更新显示时 key 起着极其重要的作用。当状态中的数据发生变化时, React 会根据新数据生成新的虚拟 DOM,随后 React 进行新虚拟 DOM 与旧虚拟 DOM 的 Diff 比较,比较规则如下:

  1. 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
    • 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM。
    • 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM。
  2. 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key:
    • 根据数据创建新的真实 DOM,随后渲染到页面。
const persons =[
	{id:1, name:'小张', age:18},
	{id:2, name:'小李', age:19},
]
const element = (
	<ul>
		{
			persons.map(item => <li key={item.id}>{item.name}:{item.age}</li>)
		}
	</ul>
)
用 index 作为 key 可能会引发的问题:
  1. 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 的更新。界面没有问题,但效率低。
  2. 如果结构中还包含输入类的 DOM,会产生错误 DOM 的更新。界面会有问题。
class App extends React.Component{
	state = {
		persons:[
			{id:1, name:'小张', age:18},
			{id:2, name:'小李', age:19},
		]
	}

	add = ()=>{
		const {persons} = this.state
		const p = {id:persons.length+1, name:'小王', age:20}
	    this.setState({persons:[p,...persons]})
	}

	render(){
		const {persons} = this.state
		return (
			<div>
				<button onClick={this.add}>添加一个小王</button>
				<ul>
					{
						persons.map((item, index)=> (
							<li key={index}>
								{item.name}:{item.age} 
								<input type="text"/>
							</li>
						))
					}
				</ul>
			</div>
		)
	}
}

在小张和小李后面的 <input> 中输入各自的信息,然后点击添加一个小王,会出现下面的情况,信息串行了。
请添加图片描述
原因是:旧的虚拟 DOM 与新的虚拟 DOM 进行对比,在旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key 值 0 和 1,React 逐层对比时发现 <li> 的文字内容变了,但是 input 这个标签节点没有变。导致了两个问题:原本能复用的小张和小李的个人信息的真实 DOM 都需要重新生成,导致了没有必要的真实 DOM 的更新;而且小张和小李的 <input> 由于复用导致了内容的串行。

// 初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},

// 初始虚拟 DOM:
<li key=0>小张:18<input type="text"/></li>
<li key=1>小李:19<input type="text"/></li>

// 更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},

// 更新后的虚拟 DOM:
<li key=0>小王:20<input type="text"/></li>
<li key=1>小张:18<input type="text"/>
</li><li key=2>小李:19<input type="text"/></li>

事件:

React 中事件的命名采用小驼峰式,通过 {} 传入一个事件处理函数,事件处理函数会在事件发生时被执行

class App extends React.Component {
	handleBtnClick = () => {
		console.log(this)
	}

	render() {
		return <button onClick={this.handleBtnClick}>按钮</button>		
	}
}
事件处理函数中的 this:

如果直接给元素的事件绑定事件处理函数,那么事件处理函数中的 this 指向 undefined。

class App extends React.Component {
	handleBtn2Click() {
		console.log(this) // undefined
	}
	render() {
		return (
			<div>
				<button id='btn1'>按钮1</button>
				// 将事件处理函数赋值给了 DOM 元素的一个事件,就相当于是赋值给了一个变量。调用的时候是直接调用,this 指向全局对象,但由于类默认都是严格模式,因此 this 指向 undefined
				<button onClick={this.handleBtn2Click}>按钮2</button>
			</div>
		)
	}
}

可以通过以下方式来解决:

  1. 通过 bind 方法给事件处理函数手动绑定 this。
    class App extends React.Component {
    	constructor() {
    		super()
    		// 在构造方法中给事件处理函数手动绑定 this
    		this.handleBtnClick = this.handleBtnClick.bind(this)
    	}
    
    	handleBtn1Click() {
    		console.log(this) // 实例对象
    	}
        
        handleBtn2Click() {
    		console.log(this) // 实例对象
    	}
    
    	render() {
    		return (
    			<div>
    				<button onClick={this.handleBtn1Click}>按钮1</button>
    				<button onClick={this.handleBtn2Click.bind(this)}>按钮2</button>
    			</div>
    		)
    	}
    }
    
  2. 将事件处理函数写成箭头函数:箭头函数没有自己的 this,会从上一层作用域中继承。
    class App extends React.Component {
    	// 将事件处理函数写成箭头函数,箭头函数没有自己的 this,会从上一层作用域中继承
    	handleBtnClick = () => {
    		console.log(this)  // 实例对象
    	}
    
    	render() {
    		return (
    			<div>
    				<button onClick={this.handleBtnClick}>按钮</button>
    			</div>
    		)
    	}
    }
    
  3. 给事件绑定一个箭头函数,在箭头函数中调用事件处理函数。

    这种语法问题在于每次渲染组件时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。

    class App extends React.Component {
    	handleBtnClick() {
    		console.log(this)
    	}
    
    	render() {
    		return (
    			// 事件需要绑定一个事件处理函数。因此可以给 onClick 绑定一个箭头函数,然后在箭头函数中调用真实的事件处理函数来处理逻辑。由于箭头函数中的 this 继承自上一作用域 render() 方法,因此 this 指向实例对象,在箭头函数中通过 this 调用 handleBtnClick,因此 handleBtnClick 中的 this 也就指向实例对象
    			<button onClick={() => {this.handleBtnClick()}}>按钮</button>
    		)
    	}
    }
    
事件处理函数中的 event 对象:

事件发生时,事件处理函数默认会接收到一个事件对象 event 作为参数。在 React 中,React 在拿到原生 event 对象之后会对其进行包装。

class App extends React.Component {
	handleBtnClick = e => {
		console.log(e)
	}

	render() {
		return (
			<button onClick={this.handleBtnClick}>按钮</button>
		)
	}
}

请添加图片描述

给事件处理函数传递参数:
  1. 通过 bind 的方式。
    class App extends React.Component {
    	// 默认会获取到事件对象 event,放置在最后
    	handleBtnClick = (id, e) => {
    		console.log(id)
    		console.log(e)
    	}
    
    	render() {
    		return (
    			<button onClick={this.handleBtnClick.bind(this, 1)}>按钮</button>
    		)
    	}
    }
    
  2. 通过箭头函数的方式。
    class App extends React.Component {
    	handleBtnClick = (e, id) => {
    		console.log(id)
    		console.log(e)
    	}
    
    	render() {
    		return (
    			// 事件对象 event 必须显式地进行传递,否则将无法获取到 event 对象。因为在 onClick 绑定的箭头函数中是默认可以获取到 event 对象的,如果此时不显示地将 event 再传递给真正的事件处理函数,那么在真正的事件处理中就无法获取到 event 对象了。
    			<button onClick={(e) => {this.handleBtnClick(e, 1)}}>按钮</button>
    		)
    	}
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值