2 Star 1 Fork 1

kgm / flutter_ref

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
BSD-3-Clause

China

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

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

特征

  • 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('清空数组'),
            ),
          ],
        ),
      ],
    ),
  );
}
BSD 3-Clause License Copyright (c) 2020, 1169886116 All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

简介

为flutter开发的响应式组合api 展开 收起
Dart
BSD-3-Clause
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Dart
1
https://gitee.com/kgm0515/flutter_ref.git
git@gitee.com:kgm0515/flutter_ref.git
kgm0515
flutter_ref
flutter_ref
master

搜索帮助