China

Ref: 这是一个支持flutter数据响应式的组合Api插件 <br/><br/>

响应式,简单,灵活,可组合,可移植性高,侵入性低。支持数据同步修改,缓存队列异步刷新。

特征

  • Ref:把数据包装成一个响应式对象
  • RefBuilder:数据修改通知对应的widget刷新
  • Ref.update:内部监听所有响应对象的读取和修改,执行副作用并更新widget
  • RefCompute:支持计算属性
  • RefWatch:数据修改,执行副作用
  • RefKey:可以很方便的代理复杂响应式对象的某一个属性值
  • refKeys: 函数,可以把一个复杂响应式对象的所有键代理到一个简单对象中,返回一个Map<dynamic, RefKey>对象
  • ...

示例项目

示例文件夹example中有一个非常好的示例项目。过来看。否则,请继续阅读。

定义一个响应式对象

您应该通过如下方式初始化一个新的Ref对象:

// 基本类型
var count = Ref(1);
var isOk = Ref(true);

// 复杂类型-列表
List<Map<String, dynamic>> list = [
  {'title': '苹果', 'price': 23},
];
var fruitList = Ref(list);

// 复杂类型-对象
Map<String, dynamic> data = {
  'info': {'name': '李白', 'age': 12},
};
var poemInfo = Ref(data);

// 自定义类型
class User {
  late int age;
  User(this.age);
}
var userInfo = Ref(UserInfo(12));

在Widget中使用

响应式数据如果想刷新widget必须在RefBuilder中使用:

RefBuilder接收一个函数作为参数。首次页面渲染执行这个函数的时候,内部读取到的响应对象会记录这个widget。当下次有任何一个响应对象发生变动,会触发副作用,并更新所有记录的widget.

class TestBase extends StatelessWidget {
  var count = Ref(20);
  add() {
    count.set(() {
      count.value++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: [
          RefBuilder(
            () => Text(count.value.toString()),
          ),
          TextButton(onPressed: add, child: Text('添加')),
        ],
      ),
    );
  }
}

响应式对象值的获取

要获取响应式对象的值必须通过xxx.value的方式

var count = Ref(1);
print(count.value) // 1

var info = Ref({'name': 'kgm'});
print(count.value['name']); // kgm

响应式对象值修改的三种方式

dart是无法监听对象属性的修改的。所以对于复杂类型来说,直接修改响应对象的属性无法触发副作用和widget更新。

请看下面的三种有效方式:

  • 方式1:适用于基本数据类型值的修改
// 基本类型
var count = Ref(1);
changeCount() {
  count.value ++
}
// 复杂数据类型
var info = Ref({'name': 'kgm'});
changeInfo() {
  // 下面这句话会触发值的读取,不是重新赋值,
  info.value['name'] = 'good name';
  // 需要添加下面这句话才会触发副作用和widget更新
  info.value = info
}
  • 方式2:适用于单个复杂类型响应对象的修改
var info = Ref({'name': 'kgm'});
changeInfo() {
  // set方法内部会主动触发副作用和widget更新
  // 内部可以返回一个值,当这个值是false的时候,将不会触发副作用和widget更新
  info.set(() {
    info.value['name'] = 'good name';
    //return false
  })
}
  • 方式3:适用于多个响应式对象的修改
var info = Ref({'name': 'kgm'});
var fruitList = Ref(['apple', 'orange']);

// update是Ref的一个静态方法
// 内部在执行的时候会收集所有的响应式对象,并触发副作用和widget更新
// 内部可以返回一个值,当这个值是false的时候,将不会触发副作用和widget更新
changeData() {
  // info,fruitList都会触发副作用和widget更新
  Ref.update(() {
    info.value['name'] = 'good name';
    fruitList.value.add('banana')
    //return false
  })
}

// update方法的第二个可选参数:UpdateOptions对象配置如下
/*
  refs: 指定一组可以触发副作用和widget更新的响应式对象
  delay: 可以设置副作用和widget更新的延时时间,这里做了防抖处理
*/
refreshInfo() {
  // 尽管fruitList也发生了修改,但是不会触发副作用和widget更新的响应式对象
  // 因为配置了delay属性,相关的副作用和widget更新将会在3s后执行,这有利于做防抖优化。
  Ref.update(() {
    info.value['name'] = 'good name';
    fruitList.value.add('banana')
  }, UpdateOptions(
    refs:[score], 
    delay: Duration(milliseconds: 3000),
  );
}

定义一个计算属性

您应该通过如下方式初始化一个RefCompute对象:

RefComputeRef的一个子类,但是计算属性自身是无法赋值的。

注意如果计算属性定义在widget内部,在widget卸载的时候必须清除依赖!

Map<String, dynamic> fatherInfo = Ref({
  'info': {'name': 'kgm', 'age': 50},
});
// 下面这个计算属性
// 父亲和儿子年龄相差20岁,根据父亲的年龄来获取儿子的年龄
var childAge = RefCompute<int>(() {
  return fatherInfo.value['info']['age'] - 20;
});

// 可以看到父亲年龄的变动会引起儿子年龄的重新计算
add() {
  fatherInfo.set(() {
    fatherInfo.value['info']['age'] += 5;
  });
}

@override
void dispose() {
  // 清除compute依赖
  childAge.dispose();
  super.dispose();
}

@override
Widget build(BuildContext context) {
  return Container(
    child: Column(
      children: [
        RefBuilder(
          () => Text( "爸爸年龄:" + fatherInfo.value['info']['age'].toString() ),
        ),
        RefBuilder(
          () => Text( "儿子年龄:" + childAge.value.toString() ),
        ),
        TextButton(onPressed: add, child: Text('爸爸老了')),
      ],
    ),
  );
}

定义一个事件监听

您想在某一个响应式对象发生变化的时候触发一些额外操作吗?

您应该通过如下方式初始化一个RefWatch对象:

注意如果定义在widget内部,在widget卸载的时候必须清除依赖!

var wacthCount = Ref(1);

// 定义一个事件监听
// RefWatch的第一个参数必须返回一个具体的值,否则无法触发副作用
var watchFun = RefWatch(() => wacthCount.value, (oldVal, newVal) {
  String desc = '';
  if (newVal == 0) {
    desc = '大鸭蛋';
  } else if (newVal < 5) {
    desc = '没及格啊';
  } else if (newVal < 8) {
    desc = '还需继续努力';
  } else if (newVal < 10) {
    desc = 'good';
  }
  score = '上一次$oldVal分,这一次$newVal分。$desc';
  print(score);
});

add() {
  if (wacthCount.value < 10) wacthCount.value++;
}

minis() {
  if (wacthCount.value > 0) wacthCount.value--;
}

@override
void dispose() {
  // 清除watch依赖
  watchFun.dispose();
  super.dispose();
}

@override
Widget build(BuildContext context) {
  return Container(
    child: Column(
      children: [
        RefBuilder( () => Text(wacthCount.value.toString())),
        TextButton(onPressed: add, child: Text('加分')),
        TextButton(onPressed: minis, child: Text('减分')),
      ],
    ),
  );
}

代理某一个复杂响应式对象的某一个属性

假设这里有一个很复杂的响应式对象:var ref = Ref({'info': {'name': '老子', 'age': 12},})

你会频繁的获取和设置里面的某个属性,如 Int age = ref.value['info']['age']

很长很烦恼是不是。那你需要用到RefKey这个api了

var data = {
  'info': {'name': '小马猴', 'age': 12},
  'arr': ['苹果', '橘子', '香蕉']
};
var keyInfo = Ref<Map<String, dynamic>>(data);

// 开始代理了------------------------
RefKey arr = RefKey(keyInfo, 'arr.0');
RefKey name = RefKey(keyInfo, 'info.name');

reset() {
  keyInfo.set(() {
    name.value = '大马猴'; // keyInfo.value['info']['name'] = '大马猴';
    arr.value = '榴莲'; // keyInfo.value['arr'][0] = '榴莲';
  });
}

@override
Widget build(BuildContext context) {
  return Container(
    child: Column(
      children: [
        RefBuilder(
          () => Column(
            children: [
              Text( name.value), // keyInfo.value['info']['name'],
              Text( arr.value )// keyInfo.value['arr'][0],
            ],
          ),
        ),
        TextButton(onPressed: reset, child: Text('重置')),
      ],
    ),
  );
}

代理某一个复杂响应式对象的所有属性

可以代理普通对象和普通列表的响应式对象, 返回一个Map<dynamic, RefKey>对象。

注意这个api用好不容易。用的不好会给你带来很多烦恼!

看下面的栗子吧!

List<Map<String, dynamic>> data = [
  {'title': '苹果', 'price': 23},
  {'title': '橘子', 'price': 12},
  {'title': '香蕉', 'price': 56},
  {'title': '西瓜', 'price': 42},
];
var keysInfo = Ref(data);

@override
Widget build(BuildContext context) {
  // 定义到这里,新增的元素不会由任何响应。因为build没有刷新
  // var arrList = refKeys<Map<String, dynamic>>(keysInfo);
  return Container(
    child: Column(
      children: [
        Column(
          children: [
            RefBuilder(() {
              // 定义到这里,新增的元素有响应。因为每次都会生成新的代理对象
              var arrList = refKeys<Map<String, dynamic>>(keysInfo);
              return Wrap(
                children: arrList.values.map((ele) {
                  return Row(
                    children: [
                      if (ele.value['show'] != false) Text(ele.value['title'] ),
                      TextButton(
                        onPressed: () {
                          ele.ref.set(() {
                            ele.value['show'] = ele.value['show'] == false ? true : false;
                          });
                        },
                        child: Text(ele.value['show'] != false ? '隐藏' : '显示'),
                      )
                    ],
                  );
                }).toList(),
              );
            }),
            TextButton(
              onPressed: () {
                keysInfo.set(() {
                  keysInfo.value.clear();
                });
              },
              child: Text('清空数组'),
            ),
          ],
        ),
      ],
    ),
  );
}

Libraries

builder
compute
effect
key
keys
ref
ref
render
tools
update
watch