alibaba / fish-redux

An assembled flutter application framework.
https://github.com/alibaba/fish-redux
Apache License 2.0
7.33k stars 843 forks source link

所有component都依赖的global state的reselect方法 #596

Closed ykrank closed 4 years ago

ykrank commented 4 years ago

现在有一个全局对象,比如User。所有的component都有可能用到,并且都会随它的修改而更新。按照fish-redux的设计,我目前是使用reselect,但意味着从page开始到所有的component的state,都需要包裹一层user。虽然我目前是用通用mixin,但是每个state都不得不包裹一层,哪怕自己没用到,子component也有可能用到,还得一层层reselect下去,从性能和设计考虑,都显得实在太臃肿了。 这个现在有什么好的解决方案?或者对框架有所修改,比如加入订阅机制,component可以订阅某个修改。或者换种方式,将connectExtraStore下移到component,使用类似的数据流。

o1298098 commented 4 years ago

其实也可以用InheritedWidget 来解决吧

ykrank commented 4 years ago

其实也可以用InheritedWidget 来解决吧

当InheritedWidget 更新,如果上层的state不更新的话,fish-redex就会使用缓存的widget,依赖InheritedWidget 的子widget会不会更新呢?

o1298098 commented 4 years ago

我写了一个简单的例子

import 'package:flutter/material.dart';

class TestInheritedWidget extends InheritedWidget {
  TestInheritedWidget({@required this.counter, Widget child})
      : super(child: child);
  final TestClass counter;

  static TestInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<TestInheritedWidget>();
  }

  @override
  bool updateShouldNotify(TestInheritedWidget oldWidget) {
    return oldWidget.counter.value != counter.value;
  }
}

class TestClass {
  String name;
  int value;
  TestClass({this.name, this.value});
}

main.dart

import 'package:flutter/material.dart';
import 'package:movie/style/test_Inherited.dart';

import 'app.dart';

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(TestInheritedWidget(
      counter: TestClass(name: 'test', value: 1), child: await createApp()));
}

test_page.dart


import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:movie/actions/adapt.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(
    TestPageState state, Dispatch dispatch, ViewService viewService) {
  return Scaffold(
    appBar: AppBar(),
    body: viewService.buildComponent('test'),
  );
}

test_component.dart


import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:movie/actions/adapt.dart';
import 'package:movie/style/test_Inherited.dart';
import 'package:movie/views/test_page/action.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(TestState state, Dispatch dispatch, ViewService viewService) {
  return Center(
      child: Column(
    children: <Widget>[
      SizedBox(height: Adapt.px(200)),
      Text(
        '${TestInheritedWidget.of(viewService.context).counter.value}',
        key:
            ValueKey(TestInheritedWidget.of(viewService.context).counter.value),
      ),
      SizedBox(height: Adapt.px(100)),
      FlatButton(
        color: Colors.amber,
        onPressed: () {
          TestInheritedWidget.of(viewService.context).counter.value++;
          dispatch(TestPageActionCreator.onAction());
        },
        child: Text('add'),
      )
    ],
  ));
}

效果如下 Kapture 2020-01-13 at 19 05 08

ykrank commented 4 years ago

我写了一个简单的例子

import 'package:flutter/material.dart';

class TestInheritedWidget extends InheritedWidget {
  TestInheritedWidget({@required this.counter, Widget child})
      : super(child: child);
  final TestClass counter;

  static TestInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<TestInheritedWidget>();
  }

  @override
  bool updateShouldNotify(TestInheritedWidget oldWidget) {
    return oldWidget.counter.value != counter.value;
  }
}

class TestClass {
  String name;
  int value;
  TestClass({this.name, this.value});
}

main.dart

import 'package:flutter/material.dart';
import 'package:movie/style/test_Inherited.dart';

import 'app.dart';

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(TestInheritedWidget(
      counter: TestClass(name: 'test', value: 1), child: await createApp()));
}

test_page.dart


import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:movie/actions/adapt.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(
    TestPageState state, Dispatch dispatch, ViewService viewService) {
  return Scaffold(
    appBar: AppBar(),
    body: viewService.buildComponent('test'),
  );
}

test_component.dart


import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'package:movie/actions/adapt.dart';
import 'package:movie/style/test_Inherited.dart';
import 'package:movie/views/test_page/action.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(TestState state, Dispatch dispatch, ViewService viewService) {
  return Center(
      child: Column(
    children: <Widget>[
      SizedBox(height: Adapt.px(200)),
      Text(
        '${TestInheritedWidget.of(viewService.context).counter.value}',
        key:
            ValueKey(TestInheritedWidget.of(viewService.context).counter.value),
      ),
      SizedBox(height: Adapt.px(100)),
      FlatButton(
        color: Colors.amber,
        onPressed: () {
          TestInheritedWidget.of(viewService.context).counter.value++;
          dispatch(TestPageActionCreator.onAction());
        },
        child: Text('add'),
      )
    ],
  ));
}

效果如下 Kapture 2020-01-13 at 19 05 08

key必须要绑定依赖数据吗

o1298098 commented 4 years ago

不需要的,你可以自己尝试下 Kapture 2020-01-14 at 14 37 45

ykrank commented 4 years ago

不需要的,你可以自己尝试下 Kapture 2020-01-14 at 14 37 45

你是不是在TestPageActionCreator.onAction()中刷新了整个page的state或者TestState呀。

ykrank commented 4 years ago

我因为user是全app通用的,所以没办法一个个页面的手动刷新,希望user改了后,用到user的component或者page会自己刷新,而不是我得一个个去手动刷新。另外将shouldUpdate改成了按equal来判断后,刷新state时也不会走到buildView,导致压根不刷新。感觉放在shouldUpdate里也是一个办法,不过没找到拿oldStore和现在的store的办法。

o1298098 commented 4 years ago

是全局刷新的,你可以自己尝试一下 account_page.dart 代码片段

 Text(
            'Hi , ${state.user?.displayName ?? 'Guest'}+${TestInheritedWidget.of(viewService.context).counter.value}',
            style: TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: Adapt.px(60),
            ),
          ),

Kapture 2020-01-14 at 15 17 10

ykrank commented 4 years ago

是全局刷新的,你可以自己尝试一下 account_page.dart 代码片段

 Text(
            'Hi , ${state.user?.displayName ?? 'Guest'}+${TestInheritedWidget.of(viewService.context).counter.value}',
            style: TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: Adapt.px(60),
            ),
          ),

Kapture 2020-01-14 at 15 17 10

试了,test直接用了你的代码还是无效,buildView不会重新进入,只有自己手动更新page的state才能触发刷新。所以我才问你TestPageActionCreator.onAction()中是否刷新了整个page的state或者TestState。 这还是我问的第一个问题,Component的shouldUpdate因为不包括这个全局数据的判断,导致返回了false。 你那边之所以有效,是因为你Reducer里clone了当前state,而默认的shouldUpdate是identical判断,那自然是返回了true,整个page都会重建的。而我现在就是需要局部重建,因此不能直接重建整个page。即使只能全部重建,也不应该是自己手动去修改page state。

o1298098 commented 4 years ago

确实,如果组件设置了shouldUpdate的条件,由于InheritedWidget的数据并不在对应组件的state中,shouldUpdate是判断不了,这样就不能刷新

zjuwjf commented 4 years ago

这种情况,简单的方法我建议也是通过 InheritedWidget 来完成。

fish-redux 在顶级InheritedWidget发生变化时,默认会触发一次缓存的清理。

  void didChangeDependencies() {
    super.didChangeDependencies();

    if (widget.component.protectedClearOnDependenciesChanged != false) {
      _ctx.clearCache();
    }

    _ctx.onLifecycle(LifecycleCreator.didChangeDependencies());
  }
ykrank commented 4 years ago

didChangeDependencies

原来如此,我看到clearOnDependenciesChanged默认是false,改成true就行了。

zjuwjf commented 4 years ago

如果 clearOnDependenciesChanged 默认是false,看上去是一个不合理的选择,我会在下个版本中,修改它的默认值。

xmsz commented 4 years ago

如果组件要用到全局的数据,有没有更好的方法。从 page 一直往下传太蛋疼了

ykrank commented 4 years ago

如果组件要用到全局的数据,有没有更好的方法。从 page 一直往下传太蛋疼了

InheritedWidget。记得将page和component的clearOnDependenciesChanged设成true。

xinpiaoyuanfang commented 4 years ago

如果组件要用到全局的数据,有没有更好的方法。从 page 一直往下传太蛋疼了

InheritedWidget。记得将page和component的clearOnDependenciesChanged设成true。

可以给个demo么?

chenkb730 commented 4 years ago

如果组件要用到全局的数据,有没有更好的方法。从 page 一直往下传太蛋疼了

InheritedWidget。记得将page和component的clearOnDependenciesChanged设成true。

请问这个方法怎么设置 我没有找到对应的入口