Vue之JSX

先聊点别的… – 基础

Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。

假设我们要生成一些带锚点的标题:

<h1>
  <a name="hello-world" href="#hello-world">
    Hello world!
  </a>
</h1>

对于上面的 HTML,你决定这样定义组件接口:

<anchored-heading :level="1">Hello world!</anchored-heading>

当开始写一个只能通过 level prop 动态生成标题 (heading) 的组件时,你可能很快想到这样实现:

<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>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>
</script>

Vue.component('anchored-heading', {
  template: '#anchored-heading-template',
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

这里用模板并不是最好的选择:不但代码冗长,而且在每一个级别的标题中重复书写了 ,在要插入锚点元素时还要再次重复。

虽然模板在大多数组件中都非常好用,但是显然在这里它就不合适了。

那么,我们来尝试使用 render 函数重写上面的例子:

Vue.component('anchored-heading', {
  render(createElement) {
    return createElement(
      'h' + this.level,   // 标签名称
      this.$slots.default // 子节点数组
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

看起来简单多了!这样代码精简很多。但是你可能会有新的发现,就这 render 中出现了一个新的 createElement ,wtf, 这都是什么跟什么?另外 createElement 中传参都是什么?,那么下面,我们来揭晓它的神秘面纱。

createElement

createElement 其实就是增加一个元素,然后返回一个 createNodeDescription ,wtf。这又是什么? 其实它就是我们经常挂在嘴边的,虚拟节点 (virtual node)或虚拟DOM ,常简写为 VNode

createElement 参数

接下来你需要熟悉的是如何在 createElement 函数中使用模板中的那些功能。这里是 createElement 接受的参数:

createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中 attribute 对应的数据对象。可选。
  {
    // (详情见下一节)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

createElement一共有三个参数,三个参数分别是:

  1. 需要渲染的组件,也可以是组件的标签 div;或者是一个组件对象,也就是你天天写的 export default {};也可以是一个异步函数。
  2. 组件的属性,是一个对象,如果组件没有参数,可以传null
  3. 组件的子组件,可以是一个字符串(textContent)或者一个由 VNodes 组成的数组

这时候我们就可以使用 createElement 来写组件了

export default {
	methods: {
		getChildrenTextContent(children) {
		  return children.map(function (node) {
		    return node.children
		      ? getChildrenTextContent(node.children)
		      : node.text
		  }).join('')
		}
	},
	render(createElement) {
		let	headingId = this.getChildrenTextContent(this.$slots.default)
						.toLowerCase()
						.replace(/\W+/g, '-')
      					.replace(/(^-|-$)/g, '');
      
		return createElement(
			'h' + this.level, // h1~h6 标签
			null, // 组件属性,可以不传
			// 子组件集合
			[
		        createElement('a', {
		          attrs: {
		            name: headingId,
		            href: '#' + headingId
		          }
		        }, this.$slots.default)
		     ]
		)
	},
	props: {
		level: {
			type: Number,
			required: true
		}
	}
}

目前到这里为止,看着还可以,但是如果使用 createElement 来创建一个 form 表单的时候,你可能会会恐怖的发现,你想放弃了:

export default {
render(createElement) {
	return createElement(
	      'form',
	      [
	        createElement(
	          'label',
	          [
	            createElement(
	            	'input', 
	            	{
						attrs: {
						   placeholder: '审批人'
						},
						on: {
						  input: () => {}
						}
	            	}
	            )
	          ]
	        ),
	        createElement(
	          'label',
	          [
	            createElement(
	              'select',
	              [
	                createElement('option', {
	                  attrs: {
	                    value: 'shanghai'
	                  }'上海'
	                }),
	                createElement('option', {
	                  attrs: {
	                    value: 'beijing'
	                  },
	                  '北京'
	                })
	              ]
	            )
	          ]
	        ),
	        createElement('label', null, [
	          createElement(
	            'button',
	            {
	              on: {
	                click: () => {}
	              }
	            },
	            '查询'
	          )
	       ])
	    ]
	)
}

看到上面的代码后,感觉是不是 “很香” ?那么怎么才能让它不这么香呢?

使用JSX

JSX 是一种 JavaScript 的语法扩展, JSX = JavaScript + XML,,就是在 JavaScript 里面写 XML ,既具备了 JavaScript 的灵活,又兼具 html 的语义化和直观性。

让我们使用JSX 来实现上面示例中的form表单:

export default {
	methods: {
		input_function(){},
		click_function(){}
	},
	render() {
		return (
			<form>
				<label>
					<input placeholder="审批人" onInput={this.input_function} />
				</label>	
				<label>
					<select>
						<option value="shanghai">上海</option>
						<option value="beijing">北京</option>
					</select>
				</label>
				<label>
					<button onClick={this.click_function}>查询</button>
				</label>
			</form>
		)
	}
}

怎么样?是不是真香了?不过在使用 JSX 时,我们的 v-model、v-for、v-if 等等指令,该怎么办呢?

v-if、v-for

v-if 可以使用v-show来代替,如果非要实现 v-if 的话,需要使用三元表达式来代替,而 v-for 需要使用 array.map 来代替:

<ul v-if="items.length">
  <li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
// v-show
props: ['show''list'],
return (
	 <ul v-show={this.show}></ul>
	  <p v-show={!this.show}>No items found.</p>
)
// 三元表达式
return (
	 this.show 
	 ? <ul>
		  {
			  list.map(item => {
			   return <li>{item}</li>
			  })
		  }
	  </ul>
  	: <p v-show={!this.show}>No items found.</p>
)

// array.map
return (
	<ul>
	  {
		  list.map(item => {
		   return <li>{item}</li>
		  })
	  }
	  </ul>
)

你可能还会想问 v-model 呢?其实 v-model 只是 value 和 onInput 的语法糖而已

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>
  }
}

同样, .sync 也是一个语法糖

export default {
  methods: {
    handleChangeVisible(value) {
      this.name = value
    }
  },
  render() {
    return (
      <custom-component
        title="测试.sync"
        name={this.name}
        on={{ 'update:name': this.handleChangeName }}
      ></custom-component>
    )
  }
}

v-bind

在模板中,我们通过 v-bind:prop=“value”:prop=“value” 来绑定属性:

render() {
	return(
		<input value={this.value} />
	)
}

v-html

在模板中,我们用 v-html 指令来更新元素的 innerHTML 内容,而在 JSX 里面,就需要用到 domPropsInnerHTML :

export default {
  data() {
    return {
      content: '<div>这是一段html代码</div>'
    }
  },
  render() {
    // v-html 指令在JSX中是 domPropsInnerHTML={html}
    return <div domPropsInnerHTML={this.content}></div>
  }
}

v-text

在模板中,我们常用 {{text}}v-text 来输出我们的文本内容,而在 JSX 中,我们可以使用 domPropsInnerText 或者 {text}

export default {
  data() {
    return {
      content: '这是一段文本代码'
    }
  },
  render() {
    // v-text 指令在JSX中是 domPropsInnerText={text}
    return <div domPropsInnerHTML={this.content}></div>
    // 也可以
    return <div>{this.content}</div>
  }
}

事件修饰符

JSX 中,如果要使用事件修饰符,有两种方法:

// .stop: 阻止事件冒泡, 可以使用 event.stopPropagation()
// .prevent: 阻止默认行为,可以受用 event.preventDefault()
// .self:只当事件是从侦听器绑定的元素本身触发时才触发回调,使用下面的条件判断进行代替
if (event.target !== event.currentTarget){
  return
}

另外其他的修饰符,Vue也提供了前缀语法:

修饰符前缀
.passive&
.capture!
.once~
.capture.once 或 .once.capture~!

使用方法如下

 render() {
    return (
      <div
        on={{
          '!click': this.handleClick,
          '~input': this.handleInput,
          '&mousedown': this.handleMouseDown,
          '~!mouseup': this.handleMouseUp
        }}
      ></div>
    )
  }

插槽

插槽分为:默认、具名、作用域三种,可以通过 this.$slots 访问插槽的内容,每个插槽都是一个 VNode 数组,。

默认插槽

this.$slots上面就挂载了一个这个组件内部的所有插槽,使用 this.$slots.default 就可以将默认插槽加入到组件内部

export default {
  render() {
    return (
      <div class="custom-component">
        {this.$slots.default}
      </div>
    )
  }
}

具名插槽

有时候我们一个组件可能需要多个插槽,这个时候就需要为每一个插槽起一个名字,比如头部的 header ,底部的 footer

 render() {
    return (
      <div>
        <div slot="header">头部</div>
        <div slot="footer">底部</div>
      </div>
    )
  }

在使用具名插槽时,如何将对应的内容插入到对应的区域呢?需要使用 this.$slots.插槽名称 :


render() {
    return (
      <div>
        <header>{this.$slots.header}</header>
        <footer>{this.$slots.footer}</footer>
      </div>
    )
  }

作用域插槽

有时候让插槽内容能够访问子组件中才有的数据是很有用的,这个时候就需要用到作用域插槽 scopedSlots

// 子组件
  name: 'childComponent',
  data() {
    return {
      name: '惠明'
    }
  },
  render() {
	  return (
	  	<div>
	  		{
	  			this.$scopedSlots.header({ 
	  				props: this.name 
	  			})
	  		}
	  		{
	  			this.$scopedSlots.default()
	  		}
	  	</div>
  }

// 父组件
import childComponent from ...
  render() {
	  const scopedSlots = {
	    // 这里的 header 是根据子组件中的命名来写的
	    // 结果 <header>惠明</header>
	  	header: props => <header>{props.name}</header>
	  	// 如果是默认插槽的话
	  	// 结果 <div>default插槽</div>
	  	default: () => <div>default插槽</div>
	  };
	  return (
	  	<child-component scopedSlots={scopedSlots} />
  }

要了解更多关于 JSX 如何映射到 JavaScript,请阅读使用文档

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值