-
Notifications
You must be signed in to change notification settings - Fork 11
Open
Description
指令实现很复杂,今天先演示一个最简单的指令red
,把元素的背景显示为红色。后续再实现表达式功能等。
属性里面支持特殊字符
首先,把使用 John Resig
的 HTML Parser
修改一下,因为我们的指令里面有 -:@.
等特殊字符。把
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/
attr = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
修改为
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[-A-Za-z0-9_@.:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*
(\/?)>/
attr = /([-A-Za-z0-9_:@.]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
修改语法树数据结构,增加指令数据结构
直接使用vue的数据结构。directives?: Array<ASTDirective>
declare type ASTElement = {
type: 1;
tag: string;
//attrsList: Array<{ name: string; value: any }>;
attrsMap: { [key: string]: any };
parent?: ASTElement;
children: Array<ASTNode>;
directives?: Array<ASTDirective>;
}
declare type ASTDirective = {
name: string;
rawName: string;
value: string;
arg: ?string;
modifiers: ?ASTModifiers;
};
declare type ASTModifiers = { [key: string]: boolean };
修改语法树生成逻辑解析指令
export function processAttrs(el, attrs) {
let i, l, name, rawName, value, modifiers, isProp
for (i = 0, l = attrs.length; i < l; i++) {
name = rawName = attrs[i].name
value = attrs[i].value
// modifiers
modifiers = parseModifiers(name);
if (modifiers) {
name = name.replace(modifierRE, '');
}
// parse arg
var argMatch = name.match(argRE);
var arg = argMatch && argMatch[1];
if (arg) {
name = name.slice(0, -(arg.length + 1));
}
// 是指令
if (isDirective(name)) {
addDirective(el, name, rawName, value, arg, modifiers);
}
}
}
function isDirective(name: String): boolean {
return name.startsWith('x-')
}
function parseModifiers(name) {
var match = name.match(modifierRE);
if (match) {
var ret = {};
match.forEach(function (m) { ret[m.slice(1)] = true; });
return ret
}
}
/**
*
* @param {*} el
* @param {*} name
* @param {*} rawName
* @param {*} value
* @param {*} arg
* @param {*} modifiers
*/
function addDirective(
el: ASTElement,
name: string,
rawName: string,
value: string,
arg: ?string,
modifiers: ?ASTModifiers
) {
(el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })
el.plain = false
}
解析完的树是这样子的,在原来的AST节点下面增加了 directives
节点。
{
"type": 1,
"tag": "div",
"attrsMap": {
"id": "hook-arguments-example",
"x-demo:foo.a.b": "message"
},
"children": [],
"directives": [
{
"name": "x-demo",
"rawName": "x-demo:foo.a.b",
"value": "message",
"arg": "foo",
"modifiers": {
"a": true,
"b": true
}
}
],
"plain": false
}
修改生成渲染函数字符串
在 ast2render
里面,增加一个函数处理指令
/**
* 解析指令
* @param {*} node
*/
function getDirectiveStr(node: any) {
let dirs = node.directives
let str = '';
if (dirs) {
str += 'directives:['
// why not use for..in, see eslint `no-restricted-syntax`
dirs.forEach(dir => {
str += JSON.stringify(dir) + ','
})
str += '],'
}
return str;
}
最终生成的渲染函数
增加了指令的信息,在渲染函数里面,增加了一个字段 directives
h("div",{attrs:{},
directives:[
{"name":"x-demo","rawName":"x-demo:foo.a.b","value":"message","arg":"foo","modifiers": {"a":true,"b":true}},
],
},[])
扩展snabbdom,使支持指令
我们原来使用的渲染函数是 snabbdom的h,我们测试发现,它返回的虚拟节点
默认已经包含了指令信息,在data里面。所以这块我们不需要更改。
接下来我们需要扩展 snabbdom 模块,增加一个处理指令模块
"use strict";
function updateDirective(oldVnode, vnode) {
var elm = vnode.elm, nodeDirs = vnode.data.directives;
if (!nodeDirs)
return;
nodeDirs = nodeDirs || {};
console.log('自定义指令处理:', vnode.context);
console.log('自定义指令处理:', vnode);
console.log('自定义指令处理:', nodeDirs);
const vm = vnode.context
const dirs = vm.directives
nodeDirs.forEach(function (dir) {
// 直接调用指令的处理函数
dirs[dir.name](vnode.elm, dir)
}, vm);
}
export default {
create: updateDirective,
update: updateDirective
};
在 snabbdom上注册
import * as snabbdom from 'snabbdom'
import * as snabbdom_class from 'snabbdom/modules/class'
import * as snabbdom_props from 'snabbdom/modules/props'
import * as snabbdom_style from 'snabbdom/modules/style'
import * as snabbdom_directive from './directives/directive'
import * as snabbdom_eventlisteners from 'snabbdom/modules/eventlisteners'
import * as snabbdom_h from 'snabbdom/h'
const patch = snabbdom.init([ // Init patch function with chosen modules
snabbdom_class.default, // makes it easy to toggle classes
snabbdom_props.default, // for setting properties on DOM elements
snabbdom_style.default, // handles styling on elements with support for animations
snabbdom_eventlisteners.default, // attaches event listeners
snabbdom_directive.default, // xiaowenjie add 处理指令
])
const h = snabbdom_h.default // helper function for creating vnodes
export { h, patch }
扩展框架代码
第一,把实例绑定到vnode中 context
,snabdom 处理的时候再拿出来
function updateComponent(vm: Xiao) {
let proxy = vm
// 虚拟dom里面的创建函数
proxy.h = h
// 新的虚拟节点
let vnode = vm.$render.call(proxy)
// 把实例绑定到vnode中,处理指令需要用到
vnode.context = vm
// 上一次渲染的虚拟dom
let preNode = vm.$options.oldvnode;
log(`[lifecycle] 第${renderCount}次渲染`)
if (preNode) {
vnode = patch(preNode, vnode)
}
else {
vnode = patch(vm.$el, vnode)
}
log('vnode', vnode)
renderCount++;
// save
vm.$options.oldvnode = vnode;
}
然后 初始化的时候需要初始化全局指令。先使用一个把背景变成红色的指令
/**
* 初始化全局指令
*/
function initGlobaleDedirectives() {
Xiao.directive('red', function (el, binding) {
el.style.backgroundColor = 'red'// binding.value
})
}
initGlobaleDedirectives()
在 框架代码中增加实例注册方法 $directive
static directive(name: string, cb: any) {
globaleDedirectives[`x-${name}`] = cb
}
$directive(name: string, cb: any) {
this.directives[`x-${name}`] = cb
}
测试
测试默认的 red
指令和自定义了一个demo
指令。
<body>
<h1>简单自定义指令</h1>
<div id="demo1" x-demo>使用<b>app.$directive</b>注册</div>
<div id="demo3" x-red>使用<b>默认的red指令</b></div>
<script>
// 自定义指令测试
var app = new Xiao();
// 自定义一个把背景变成黄色的指令
app.$directive('demo', function (el, binding) {
el.style.backgroundColor = 'yellow'// binding.value
})
app.$mount("#demo1");
new Xiao({
el:"#demo3"
});
</script>
</body>
生成的vnode信息
{
"sel": "div",
"data": {
"attrs": {},
"directives": [
{
"name": "x-demo",
"rawName": "x-demo",
"value": "",
"arg": null
}
]
},
"children": [
{
"text": "使用",
"elm": {}
},
{
"sel": "b",
"data": {
"attrs": {}
},
"children": [
{
"text": "app.$directive",
"elm": {}
}
],
"elm": {}
},
{
"text": "注册",
"elm": {}
}
],
"elm": {},
"context": null
}
Metadata
Metadata
Assignees
Labels
No labels