一次学俩Vue&Blazor:1.4基础-响应式数据

一、声明式编程和响应式数据


1、声明式编程

  • 逻辑层修改视图层元素属性值的方式有两种,一是命令式,先通过getElementById等方法获取元素对象,然后再修改对象的属性;二是声明式,先将视图层元素的属性值和逻辑层数据绑定,通过修改逻辑层数据,实现视图层元素属性值的自动更新。
  • 现代前端开发框架,都采用声明式。

2、响应式数据

  • 使用声明式编程的开发框架,需要通过特定的机制通知视图层更新。
  • Vue通过Proxy代理机制,劫持读取或修改数据的行为,进而通知使用到数据的UI节点进行更新,被Proxy代理的数据,就称之为响应式数据,在组合式API中,Vue提供了ref和reactive两个API来创建响应式数据。
  • 而Blazor中的字段或属性,不需要特殊处理,天然具有响应式特性,其内在机制,暂不清楚。.NET的另一个响应机制MVVM,则是通过事件机制,来通知UI层更新。


二、Vue定义响应式数据的方法


Vue提供reactive和ref两个API来创建响应式数据,只有响应式数据才具有通知更新功能。实际应用中,有几个点需要特别注意:

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来访问
<template>
  <h1>{{a.name}}</h1>
</template>

<script setup>
import {ref} from 'vue'
const a = ref({name:”MC”,age:18})
a.value.name 
</script>

4、当使用ref创建对象类型时,会调用reactive来创建value属性,类似于这种感觉ref(reactive(value))。当整体替换value值时,新值仍然是响应式的,而reactive如果整体替换新值,则会失去响应性。

  • 在实际应用中,创建对象数组类型时,推荐使用ref,因为ref创建的对象,使用数组的map、filter、reduce等返回新数组的方法时,新数组仍然可以保持响应性。
  • 使用模块化开发时,export出来的数据,也应该使用ref,这样在引用这个API时,解构出来的数据仍然具有响应性。
  • 官方文档有一句话“为了解决reactive带来的限制,Vue 也提供了一个ref方法来允许我们创建可以使用任何值类型的响应式ref”。创建响应式数据时,除了对象类型,其它类型都请尽量使用ref,虽然.value麻烦点。
 //整体替换,a失去响应性
const a = reactive({name:”MC”,age:18})
a = reactive{name:”Fun”,age:16}

//ref可以实现响应式替换,b仍具有响应性
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,即使使用filter等方法,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})

5、Vue的响应式原理

无论是选项式API的date()方法,还是组合式API的ref和reactive,都会将数据包装为Proxy对象。如果熟悉C#的属性,代理机制其实很简单。

//需要被代理的数据
const obj = {
  name:"functionMC",
  age:18
}

//当读取或修改数据时,指定代理的行为
//当读取数据时,调用get方法; 当修改数据时,调用set方法
//参数说明:target-被代理的对象、prop-读取的属性、receiver-代理对象、value-新值
const handler = {
  get(target,prop,receiver){
    
    //通过代理读取属性时,在返回值之前,它会做一个跟踪的操作,标记哪些地方用到了这个数据
    //track(),追踪谁用了这个属性......

    return target[prop]
  },

  set(target,prop,value,receiver){
    target[prop] = value

    //通过代理修改值之后,做一些其它操作
    //trigger(),触发所有使用了该值的位置进行更新
  }
};

//为数据obj,创建一个代理对象proxy
//之后对数据obj的读取和修改,都通过代理proxy来完成
const proxy = new Proxy(obj,handler)


三、Blazor中的响应式数据


Blazor中,字段和属性天然具有响应性,对象也是深层次绑定。利用属性的getter和setter方法,可以对响应式数据进行更复杂的控制。

@page "/"

<p>@Num</p>
<MButton @onclick="AddNum">增加</MButton>

<p>@employee.Name</p>
<MButton @onclick="ChangeName">更改名称</MButton>

<p>@employee.Address.City</p>
<MButton @onclick="ChangeCity">更改城市</MButton>

@foreach (var sport in sports)
{
    <p>@sport</p>
}
<MButton @onclick="AddSport">增加运动</MButton>

@code{
    //值类型的属性绑定
    public int Num { get; set; }

    //对象类型的字段绑定(深层次响应)
    private Employee employee = new Employee { 
        Id = 1, 
        Name = "functionMC", 
        Address = new Address { Province = "广东", City = "广州" } };

    //集合类型对象的字段绑定
    private List<string> sports = new List<string> { "Running", "Swiming", "BasketBall" };

    private void AddNum()
    {
        Num++;
    }

    private void ChangeName()
    {
        employee.Name = "MC";
    }

    private void ChangeCity()
    {
        employee.Address.City = "深圳";
    }

    private void AddSport()
    {
        sports.Add("FootBall");
    }
}


posted @ 2023-02-15 22:36  functionMC  阅读(498)  评论(0编辑  收藏  举报