Blazor和Vue对比学习(基础1.2):模板语法和Razor语法
Vue使用模板语法,Blazor使用祖传的Razor语法,从逻辑和方向上看,两者极为相似,比如:
- 都基于HTML
- 都通过声明式地将组件实例的状态(数据/方法)绑定到呈现的DOM上
- 都通过指令实现更加丰富的视图/HTML与逻辑/JS和C#的互动应用
- 底层机制都是通过虚拟DOM,实现差量更新
- 工程组建方式都基于组件树
- 都具有单文件组件特征
但在具体实现和语法上,两者有比较大的差异。给人的总体感觉就是,都很熟悉,但就是不太一样。以下仅对语法基础进行逐点比较,内容较多,目录如下:
- 标签内容绑定(单向)
- 标签属性绑定(单向)
- 控制结构(判断/循环等)
- 指令体系概述
- 补充:Vue的响应式约束
1、标签内容绑定(单向)
这是最基本的数据绑定形式,可以实现HTML标签体内容和逻辑代码的动态绑定。【单向】更新逻辑代码时,标签内容会自动更新。Vue和Blazor的标签体内容绑定语法如下所示:
- Vue使用双大括号,{{ /*这里是JS表达式*/ }},如{{ number+1 }}
- Blazor使用@符,@/*这里是C#表达式*/,如@(number+1),这里需要使用()显示标注表达式
- 注:表达式可以使用常量、变量、方法调用、API调用、运算符的任意合法组合
(1)最简单的变量
//Vue===================================== Message: {{ msg }} //Blazor==================================== Message: @msg @code { private string msg = “你好,我是functonMC”; }
(2)运算表达式
//Vue===================================== 结果是:{{ number + 1 }} //Blazor==================================== @(number+1) @code { private int number = 10; }
(3)调用API的表达式
//Vue===================================== {{ message.split("").reverse().join("") }} //Blazor==================================== @(string.Join("",message.ToCharArray().Reverse().ToArray())) @code { private string message = "Hello,functionMC"; }
(4)三元表达式
//Vue===================================== {{ ok ? "好的" : "不行" }} //Blazor==================================== @(ok?"好的":"不行") @code { private string message = "Hello,functionMC"; }
(5)方法调用
//Vue===================================== {{ addResult(1, 2) }} //Blazor=================================== @AddResult(1,2) @code { private int AddResult(int a,int b) { return a + b; } }
2、标签属性绑定(单向)
和标签体内容绑定一样,标签属性也可以绑定逻辑代码,可以使用常量、变量、方法调用、API调用、运算符的任意合法组合的表达式。Vue需要使用v-bind指令(可以简写为冒号),Blazor仍然使用@符,表达式的使用“标签内容”绑定基本一致。两者的语法如下所示:
- Vue:,简写为
- Blazor:,可以省略引号为
可以注意到Vue的冒号和Blazor@符所在位置的区别。所以在Blazor中,你是很容易区别指令、标签属性绑定和标签内容绑定三者的区别的,比如style="@TitleStyle"是标签属性绑定,@key="people.Id"是指令key。而在Vue中,这并不好区别,如:style="titleStyle"是标签属性绑定,:key="people.id"是指令呢?还是属性绑定呢?虽然这种区分,然并卵,但你能感觉到Blazor有着C#的严谨支撑,整个组件体系,也是基于C#的语法体系,Razor和C#之间是很容易打通的,源码比较容易看懂。而Vue在灵活的JS之上又做了一层抽象,总让人感到失去了语言的一惯性,现在的组合式API会好点,Vue2时代的选项式API,这种感觉更甚,甚至有人说,学了Vue后,都快忘了JS了。
言归正传,标签属性绑定的应用,主要涉及到样式绑定、表单组件的双向绑定、父子组件的数据传递、以及指令体系,每个在后续都会另起章节详述,所以暂且略过!
3、控制结构(条件/循环)
这里的控制结构,主要指DOM结构的条件渲染和循环渲染。Vue需要使用指令来完成,用于条件的v-if/v-else-if/v-else/v-show,用于循环的v-for;而Blazor则灵活很多,因为在Blazor中,html和C#可以混写,所以你就感觉是在写C#一样,这和react倒是很像。
(1)条件渲染
//Vue中使用v-if=============================== //根据绑定的type值,只渲染判断为true的DOM节点,其它全部干掉。如果type值在运行时频繁改变,开销将比较大,这种情况推荐使用v-showif="type === 'A'">Aelse-if="type === 'B'">Belse-if="type === 'C'">Celse>Not A/B/C//Vue中使用v-show============================= //使用v-show时,所有节点都有渲染,只是改变的style的display属性,所以运行时频繁改变type值时,开销会少很多。ABCNot A/B/C//Blazor中使用if=============================== @if (type == "A") {A} else if (type == "B") {B} else if (type == "C") {C} else {not A/B/C} @code { private string type = "g"; } //Blazor中使用switch============================ @switch (type) { case "A":Abreak; case "B":Bbreak; case "C":Cbreak; default:not A/B/Cbreak; } @code { private string type = "g"; } //Blazor中实现类似Vue的v-show=====================ABCnot A/B/C@code { private string type = "A"; }
(2)循环渲染
//Vue使用v-for指令============================= //可以循环渲染数组和类数组,类数组包括了对象、字符串、整数等 //循环对象数组,同时拿到索引。也可以v-for=“item in items1"for="(item, index) in items1" :key="item.id"> {{ index + 1 }}-{{ item.name }} //循环对象,按顺序拿到value,key和indexfor="(value, key, index) in items2" :key="key"> {{ key }}-{{ value }}-{{ index }} //循环一个整数for="n in 10" :key="n"> {{ n }} //循环一个字符串for="n in 'hello,functionMC'" :key="n"> {{ n }} //Blazor中可以使用所有C#的控制语句=================== //使用foreach循环 @foreach (var item in peoples) {
4、指令体系概览
Vue和Blazor都有指令,指令本质上是内置宏(即一组命令),从这个角度理解,Vue在逻辑代码中使用的defineProps、defineEmits、defineExpose,算不算指令?对比Vue,Blazor的指令的划分、语法格式和使用,更加规范,也更加广泛。但目前为止,Blazor还不能够自定义指令,而Vue可以(单独章节来说)。下面对两个框架的指令,进行概述,后续章节再和其它知识点做更深入的总结
(1)Blazor的指令:可以划分为三类,Razor文件/组件级别指令、组件/标签级别指令、DOM事件指令
//1、Razor文件/组件级别的指令。Razor文件/组件,本质上是一个类,这些指令主要作用于类。除了@code外,其它指令所在位置都在文件头。 @code //c#代码块,可以定义多个 @page //路径,代码层面是作用于类的特性 @layout //母板,代码层面是作用于类的特性 @inject //依赖注入 @implements //实现接口 @inherits //继承基类 @attribute //标注特性 @typeparam //类的泛型 @namespace //所属命名空间 //2、组件或HTML标签级别的指令。这些指令都定义在标签的属性名位置,以@符开头,如 @ref //引用组件或HTML标签 @key //作为组件或HTML标签的唯一标识,主要在循环渲染时使用 @attributes //将字典扩展为组件或HTML标签属性 @bind //实现双向绑定 //3、HTML的DOM事件指令。这部分比较多,主要分为焦点、鼠标、拖动、键盘、输入、剪切板、触摸、指针、多媒体、进度、其它。需要注意几个点: //①如果我们把指令视为类,那么就还可以通过指令属性来定义指令的特殊形为。Blazor中称为指令属性,Vue中叫指令修饰符。在Blazor中,使用麻烦点,需要重复一次指令。"SomeFunction">//②事件回调可以默认获得DOM事件参数,但需要注意,不同事件类别,事件参数类型不一样,比如鼠标事件为MouseEventArgs,输入事件为ChangeEventArgs等。如果要传入事件参数以外的参数,需要使用以下形式: @code{ private int AddCount(MouseEventArgs e,int a,int b){return a+b;} }"AddCount" @onclick:stopPropagation>
(2)Vue的指令特指v-开头的那十几个
//1、条件循环渲染 //①循环渲染
aaa
')5、补充:Vue的响应式约束
Vue通过reactive和ref,这两个API来创建响应式数据,实现html视图和js逻辑的数据绑定。实际应用中,有几个点需要特别注意:
(1)reactive只能用来创建对象类型(如对象、数组、Map、Set),不能创建原始类型(如string、number、boolean等)。而ref可以创建任何类型。
const a = reactive({name:'MC',age:18}) //正确
const b = reactive(18) //错误
const a = ref({name:'MC',age:18}) //正确
const b = ref(18) //正确
(2)reactive创建的响应式对象,默认是深层次的,所以里面嵌套的数据都是响应式的。
const a = reactive({}) a.people = {name:'MC',age:18} //增加的people属性也是响应式
(3)当使用ref时,值保存在ref对象的value属性上。如果是在逻辑代码里读取或修改,需要通过访问value属性,如b.value+=2;如果在模板中读取或修改,会自动解包,不需要.value
//逻辑代码中,需要通过.value来访问。模板中,自动解包,不需要.value{{a.name}}
(4)当使用ref创建对象类型时,会调用reactive来创建value属性,类似于这种感觉ref(reactive(value)),所以替换value值时,新值仍然是响应式的,而reactive如果替换新值,则会失去响应性。所以在实际应用中,创建对象数组类型时,需要使用ref,因为ref创建的对象,使用数组的map、filter、reduce等返回新数组的方法时,新数组仍然可以保持响应性。除此之外,创建分离的组合式API时,暴露出来的数据,也应该使用ref,这样在引用这个API时,解构出来的数据仍然具有响应性。
//这样是行不通的 const a = reactive({name:”MC”,age:18}) a = reactive{name:”Fun”,age:16} //ref才可以实现响应式替换 const b = ref({name:”MC”,age:18}) b.value = {name:”Fun”,age:16} //ref实现响应式解构 const obj1 = {foo: ref(1),bar: ref(2)} const {foo,bar} = obj1 //响应式解构 //使用ref,b还是响应式 const b = ref([ {name:”MC1”,age:18}, {name:”MC2”,age:19}, {name:”MC3”,age:20}]) b.value = b.value.filter((e)=>{return e.age >18}) //换成reactive,b失去了响应式 const b = reactive([ {name:”MC1”,age:18}, {name:”MC2”,age:19}, {name:”MC3”,age:20}]) b = b.filter((e)=>{return e.age >18})
总结:官方文档有一句话“为了解决reactive带来的限制,Vue 也提供了一个ref方法来允许我们创建可以使用任何值类型的响应式ref”。创建响应式数据时,我推荐尽量使用ref,虽然.value麻烦点。