Vue の JSX とは何ですか?次の記事では、Vue での JSX の概要、JSX を使用するタイミング、Vue2 での基本的な使い方について説明します。
JSX は Javascript の構文拡張であり、Javascript## のすべての機能を備えています。 # 関数でありながら、
html のセマンティクスと直観性も備えています。これにより、JS でテンプレート構文を記述することができます:
const el = <div>Vue 2</div>;
vuejs ビデオ チュートリアル 、Web フロントエンド開発 ]
level プロパティを通じてのみ動的に生成できる場合、すぐに次のように実装することを考えるかもしれません:
<script type="text/x-template" id="anchored-heading-template"> <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-else-if="level === 2"> <slot></slot> </h2> <h3 v-else-if="level === 3"> <slot></slot> </h3> </script>
const App = { render() { const tag = `h${this.level}` return <tag>{this.$slots.default}</tag> } }
render 関数を記述すると、次のコードを書くのが非常に面倒に感じるかもしれません。
createElement( 'anchored-heading', { props: { level: 1 } }, [ createElement('span', 'Hello'), ' world!' ] )
<anchored-heading :level="1"> <span>Hello</span> world! </anchored-heading>
import AnchoredHeading from './AnchoredHeading.vue' new Vue({ el: '#demo', render: function (h) { return ( <AnchoredHeading level={1}> <span>Hello</span> world! </AnchoredHeading> ) } })
Message.alert({ messge: '确定要删除?', type: 'warning' })
message でいくつかのスタイルをカスタマイズできることを願っています、現時点では、
Message.alert で
JSX をサポートする必要があるかもしれません (もちろん、slot/
html や他のメソッドを使用して解決することもできます)
Message.alert({ messge: <div>确定要删除<span style="color:red">xxx</span>的笔记?</div>, type: 'warning' })
.vue ファイルに記述できるコンポーネントは 1 つだけです。これは、シナリオによっては不便な場合があります。多くの場合、ページを記述するときに、実際には次のことが必要になることがあります。いくつかの小さなノードのフラグメントを分割し、それらを小さなコンポーネントで再利用します。これらの小さなコンポーネントは、実際には単純な関数コンポーネントを書くことで解決できます。通常、SFC の制限により、すべてを 1 つのファイルに記述することに慣れているかもしれませんが、この方法を試してみてもよいと言わざるを得ません。
// 一个文件写多个组件 const Input = (props) => <input {...props} /> export const Textarea = (props) => <input {...props} /> export const Password = (props) => <input type="password" {...props} /> export default Input
index.js のエントリ ファイルが追加されて 3 つのコンポーネントをエクスポートすることになります。
render 関数にコンパイルされます。
レンダリング関数は、実行時段階の伝説的な
仮想 DOM です。
@vue/babel-preset-jsx と
@vue/babel-helper-vue-jsx-merge-props に依存する必要があります。前者のパッケージは JSX の構文のコンパイルを担当し、後者のパッケージはランタイム
mergeProps 関数を導入するために使用されます。
npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
module.exports = { presets: ['@vue/babel-preset-jsx'], }
<h1>{{ msg }}</h1>
const name = 'Vue' const element = <h1>Hello, { name }</h1>
2 2 、
user.firstName、
formatName(user) など。
在模板代码里面我们通过v-for
去遍历元素,通过v-if
去判断是否渲染元素,在JSX中,对于v-for
,可以使用for
循环或者array.map
来代替,对于v-if
,可以使用if-else
语句,三元表达式
等来代替
使用if-else
语句
const element = (name) => { if (name) { return <h1>Hello, { name }</h1> } else { return <h1>Hello, Stranger</h1> } }
使用三元表达式
const element = icon ? <span class="icon"></span> : null;
使用数组的map方法
const list = ['java', 'c++', 'javascript', 'c#', 'php'] return ( <ul> {list.map(item => { return <li>{item}</li> })} </ul> )
在模板代码中,一般通过 v-bind:prop="value"
或:prop="value"
来给组件绑定属性,在JSX
里面就不能继续使用v-bind指令了,而是通过单大括号的形式进行绑定:
const href = 'https://xxx.com' const element = <a href={href}>xxx</a>
const properties = {a: 1, b: 2}
此外,模板代码中能通过<div v-bind="properties"></div>
批量绑定标签属性。
在JSX中也有相应的替换方案:<div {...properties}></div>
。
class绑定同样也是使用单大括号的形式
const element = <div className={`accordion-item-title ${ disabled ? 'disabled' : '' }`}></div> const element = <div class={ [ 'accordion-item-title', disabled && 'disabled' ] } >Item</div>
style绑定需要使用双大括号
const width = '100px' const element = <button style={{ width, fontSize: '16px' }}></button>
在模板代码中通过v-on指令监听事件,在JSX中通过on
+ 事件名称的大驼峰写法来监听,且绑定事件也是用大括号,比如click事件要写成onClick
,mouseenter事件要写成onMouseenter
const confirm = () => { // 确认提交 } <button onClick={confirm}>确定</button>
有时候我们希望可以监听一个组件根元素上面的原生事件,这时候会用到.native
修饰符,但是在JSX中同样也不能使用,不过也有替代方案,监听原生事件的规则与普通事件是一样的,只需要将前面的on
替换为nativeOn
,如下
render() { // 监听下拉框根元素的click事件 return <CustomSelect nativeOnClick={this.handleClick}></CustomSelect> }
除了上面的监听事件的方式之外,我们还可以使用对象的方式去监听事件
render() { return ( <ElInput value={this.content} on={{ focus: this.handleFocus, input: this.handleInput }} nativeOn={{ click: this.handleClick }} ></ElInput> ) }
对于 .passive
、.capture
和 .once
这些事件修饰符,Vue 提供了相应的前缀可以用于 on
:
例如:
on: { '!click': this.doThisInCapturingMode, '~keyup': this.doThisOnce, '~!mouseover': this.doThisOnceInCapturingMode }
对于所有其它的修饰符,私有前缀都不是必须的,因为你可以在事件处理函数中使用事件方法:具体可查阅Vue规范文档。
大多数指令并不能在JSX中使用,对于原生指令,只有v-show
是支持的。
而v-model
是Vue
提供的一个语法糖,它本质上是由 value
属性(默认) + input
事件(默认)组成的,所以,在JSX
中,我们便可以回归本质,通过传递value
属性并监听input
事件来手动实现数据的双向绑定:
export default { data() { return { name: '' } }, methods: { // 监听 onInput 事件进行赋值操作 handleInput(e) { this.name = e.target.value } }, render() { // 传递 value 属性 并监听 onInput事件 return <input value={this.name} onInput={this.handleInput}></input> } }
此外,在脚手架vue-cli4
中,已经默认集成了对v-model
的支持,可以直接使用<input v-model={this.value}>
,如果项目比较老,也可以安装插件babel-plugin-jsx-v-model
来进行支持。
同样的,在JSX
中,对于.sync
也需要用属性+事件来实现,如下代码所示:
export default { methods: { handleChangeVisible(value) { this.visible = value } }, render() { return ( <ElDialog title="测试.sync" visible={this.visible} on={{ 'update:visible': this.handleChangeVisible }} ></ElDialog> ) } }
(1)默认插槽:
使用element-ui
的Dialog
时,弹框内容就使用了默认插槽,在JSX
中使用默认插槽的用法与普通插槽的用法基本是一致的,如下
render() { return ( <ElDialog title="弹框标题" visible={this.visible}> {/*这里就是默认插槽*/} <div>这里是弹框内容</div> </ElDialog> ) }
自定义默认插槽:
在Vue
的实例this
上面有一个属性$slots
,这个上面就挂载了一个这个组件内部的所有插槽,使用this.$slots.default
就可以将默认插槽加入到组件内部
export default { props: { visible: { type: Boolean, default: false } }, render() { return ( <div class="custom-dialog" vShow={this.visible}> {/**通过this.$slots.default定义默认插槽*/} {this.$slots.default} </div> ) } }
(2)具名插槽
有时候我们一个组件需要多个插槽,这时候就需要为每一个插槽起一个名字,比如element-ui
的弹框可以定义底部按钮区的内容,就是用了名字为footer
的插槽
render() { return ( <ElDialog title="弹框标题" visible={this.visible}> <div>这里是弹框内容</div> {/** 具名插槽 */} <template slot="footer"> <ElButton>确定</ElButton> <ElButton>取消</ElButton> </template> </ElDialog> ) }
自定义具名插槽:
在上节自定义默认插槽时提到了$slots
,对于默认插槽使用this.$slots.default
,而对于具名插槽,可以使用this.$slots.footer
进行自定义
render() { return ( <div class="custom-dialog" vShow={this.visible}> {this.$slots.default} {/**自定义具名插槽*/} <div class="custom-dialog__foolter">{this.$slots.footer}</div> </div> ) }
(3)作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的,这时候就需要用到作用域插槽,在JSX
中,因为没有v-slot
指令,所以作用域插槽的使用方式就与模板代码里面的方式有所不同了。比如在element-ui
中,我们使用el-table
的时候可以自定义表格单元格的内容,这时候就需要用到作用域插槽
data() { return { data: [ { name: 'xxx' } ] } }, render() { return ( {/**scopedSlots即作用域插槽,default为默认插槽,如果是具名插槽,将default该为对应插槽名称即可*/} <ElTable data={this.data}> <ElTableColumn label="姓名" scopedSlots={{ default: ({ row }) => { return <div style="color:red;">{row.name}</div> } }} ></ElTableColumn> </ElTable> ) }
自定义作用域插槽:
使用作用域插槽不同,定义作用域插槽也与模板代码里面有所不同。加入我们自定义了一个列表项组件,用户希望可以自定义列表项标题,这时候就需要将列表的数据通过作用域插槽传出来。
render() { const { data } = this // 获取标题作用域插槽 const titleSlot = this.$scopedSlots.title return ( <div class="item"> {/** 如果有标题插槽,则使用标题插槽,否则使用默认标题 */} {titleSlot ? titleSlot(data) : <span>{data.title}</span>} </div> ) }
只需要导入进来,不用再在components属性声明了,直接写在jsx中:
import MyComponent from './my-component' export default { render() { return <MyComponent>hello</MyComponent> }, }
我们可以定义method
,然后在method
里面返回JSX
,然后在render
函数里面调用这个方法,不仅如此,JSX
还可以直接赋值给变量,比如:
methods: { renderFooter() { return ( <div> <ElButton>确定</ElButton> <ElButton>取消</ElButton> </div> ) } }, render() { const buttons = this.renderFooter() return ( <ElDialog visible={this.visible}> <div>内容</div> <template slot="footer">{buttons}</template> </ElDialog> ) }
假设该消息聊天记录的消息类型只有三种:文本,图片,引用。一条消息里面可以包括任意类型的内容,引用类型消息内部可以不断嵌套引用其他任意类型消息。效果图大致如下:
消息数据结构如下:
message: [ // 每个数组的第一个参数为消息类型:0:文本 1:图片 2:引用。第二个参数为具体内容 [ 0, '文本' ], [ 1, '图片链接xxx' ], [ 2, [ [ 0, '引用文本文本文本' ], [ 1, '引用图片链接xxx' ] ] ] ]
主要有两个思路:
1、思路一:在render里返回一段用array.map渲染的消息模板,对于三种消息类型,使用if-else进行判断分别渲染,对于引用类型的消息,可以封装一个方法进行渲染,方法里面如果还有引用类型消息就继续递归渲染。
methods: { // 展示引用消息 showQuote (msg) { return ( <div class="content-quote"> <span class="quote-title">引用:</span> {msg.map(item => { if (item[0] === 0) { return <p class="content-text">{item[1]}</p> } else if (item[0] === 1) { return ( <el-image class="content-img" src={item[1]} preview-src-list={[item[1]]}> </el-image> ) } else { return this.showQuote(item[1]) } })} </div> ) } }, render (h) { return ( <ul class="chat-record-list"> {this.recordList.map(item => { return ( <li class="chat-record-item" key={item.timeStamp} > <div class="title"> <span class="person-info"> { `${item.sendUserNick}(${item.sendUserNet}) → ${item.receiverNick}(${item.receiverNet})` } </span> <span class="sendtime"> { this.formatTime('YYYY-mm-dd HH:MM:SS', item.timeStamp) } </span> </div> <div class="content"> {item.message.map(msg => { if (msg[0] === 0) { return <p class="content-text">{msg[1]}</p> } else if (msg[0] === 1) { return ( <el-image class="content-img" src={msg[1]} preview-src-list={[msg[1]]}> </el-image> ) } else { // 递归渲染引用类型消息 return this.showQuote(msg[1]) } })} </div> </li> ) })} </ul> ) }
2、思路二:第一种思路中封装的showQuote里面的代码与render中渲染消息内容的代码基本相似,因此其实现方式不够优雅。其实可以将整个消息的渲染封装成一个组件,在该组件内引入自己,然后再渲染自己。由于具体细节代码与上述类似,这里只给出思路代码,具体细节请忽略
// 当前组件就是RecordMessage组件,自己引入自己 import RecordMessage from './RecordMessage.vue' export default { props: { message: { type: Array, default: () => [] } }, render () { const parseMessage = msg => { const type = msg[0] if (type === 0) { // 文本 } else if (type === 2) { // 图片 } else { // 引用类型 return ( <div> <div>引用:</div> { msg[1].map(subMsg => ( // 自己递归渲染自己 <recored-message> </recored-message> )) } </div> ) } } return parseMessage(this.message) }
以上がVue の JSX とは何ですか?いつ使用しますか?使い方?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。