flutter_hooks 正在参加 2020 年度 OSC 中国开源项目评选,请投票支持!
flutter_hooks 在 2020 年度 OSC 中国开源项目评选 中已获得 {{ projectVoteCount }} 票,请投票支持!
投票让它出道
已投票
flutter_hooks 获得 2020 年度 OSC 中国开源项目评选「最佳人气项目」 !
flutter_hooks 获得 2020 年度 OSC 中国开源项目评选「最佳人气项目」「最积极运营项目」 !
flutter_hooks 获得 2020 年度 OSC 中国开源项目评选「最积极运营项目」 !
授权协议 MIT
开发语言 Dart 查看源码 »
操作系统 Android
软件类型 开源软件
开源组织
地区 不详
投 递 者 程六金
适用人群 未知
收录时间 2019-01-03

软件简介

基于 React hooks 实现的 Flutter hooks。Flutter hooks 用于管理 Flutter Widgeet。有利于增加小部件之间的代码共享,可以代替 StatefulWidget 。

Flutter Hooks

A flutter implementation of React hooks: https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889

Hooks are a new kind of object that manages a Widget life-cycles. They exist for one reason: increase the code sharing between widgets and as a complete replacement for StatefulWidget.

Motivation

StatefulWidget suffer from a big problem: it is very difficult to reuse the logic of say initState or dispose. An obvious example is AnimationController:

class Example extends StatefulWidget {
  final Duration duration;

  const Example({Key key, @required this.duration})
      : assert(duration != null),
        super(key: key);

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
  }

  @override
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.duration != oldWidget.duration) {
      _controller.duration = widget.duration;
    }
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

All widgets that desire to use an AnimationController will have to reimplement almost of all this from scratch, which is of course undesired.

Dart mixins can partially solve this issue, but they suffer from other problems:

  • A given mixin can only be used once per class.

  • Mixins and the class shares the same object. This means that if two mixins define a variable under the same name, the end result may vary between compilation fail to unknown behavior.


This library propose a third solution:

class Example extends HookWidget {
  final Duration duration;

  const Example({Key key, @required this.duration})
      : assert(duration != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = useAnimationController(duration: duration);
    return Container();
  }
}

This code is strictly equivalent to the previous example. It still disposes the AnimationController and still updates its duration when Example.duration changes. But you're probably thinking:

Where did all the logic go?

That logic moved into useAnimationController, a function included directly in this library (see https://github.com/rrousselGit/flutter_hooks#existing-hooks). It is what we call a Hook.

Hooks are a new kind of objects with some specificities:

  • They can only be used in the build method of a HookWidget.

  • The same hook is reusable an infinite number of times The following code defines two independent AnimationController, and they are correctly preserved when the widget rebuild.

Widget build(BuildContext context) {
  final controller = useAnimationController();
  final controller2 = useAnimationController();
  return Container();
}
  • Hooks are entirely independent of each other and from the widget. Which means they can easily be extracted into a package and published on pub for others to use.

Principle

Similarily to State, hooks are stored on the Element of a Widget. But instead of having one State, the Element stores a List<Hook>. Then to use a Hook, one must call Hook.use.

The hook returned by use is based on the number of times it has been called. The first call returns the first hook; the second call returns the second hook, the third returns the third hook, ...

If this is still unclear, a naive implementation of hooks is the following:

class HookElement extends Element {
  List<HookState> _hooks;
  int _hookIndex;

  T use<T>(Hook<T> hook) => _hooks[_hookIndex++].build(this);

  @override
  performRebuild() {
    _hookIndex = 0;
    super.performRebuild();
  }
}

For more explanation of how they are implemented, here's a great article about how they did it in React: https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e

Rules

Due to hooks being obtained from their index, there are some rules that must be respected:

DO call use unconditionally

Widget build(BuildContext context) {
  Hook.use(MyHook());
  // ....
}

DON'T wrap use into a condition

Widget build(BuildContext context) {
  if (condition) {
    Hook.use(MyHook());
  }
  // ....
}

DO always call all the hooks:

Widget build(BuildContext context) {
  Hook.use(Hook1());
  Hook.use(Hook2());
  // ....
}

DON'T aborts build method before all hooks have been called:

Widget build(BuildContext context) {
  Hook.use(Hook1());
  if (condition) {
    return Container();
  }
  Hook.use(Hook2());
  // ....
}

About hot-reload

Since hooks are obtained from their index, one may think that hot-reload while refactoring will break the application.

But worry not, HookWidget overrides the default hot-reload behavior to work with hooks. Still, there are some situations in which the state of a Hook may get reset.

Consider the following list of hooks:

Hook.use(HookA());
Hook.use(HookB(0));
Hook.use(HookC(0));

Then consider that after a hot-reload, we edited the parameter of HookB:

Hook.use(HookA());
Hook.use(HookB(42));
Hook.use(HookC());

Here everything works fine; all hooks keep their states.

Now consider that we removed HookB. We now have:

Hook.use(HookA());
Hook.use(HookC());

In this situation, HookA keeps its state but HookC gets a hard reset. This happens because when a refactoring is done, all hooks after the first line impacted are disposed. Since HookC was placed after HookB, is got disposed.

How to use

There are two ways to create a hook:

  • A function

Functions is by far the most common way to write a hook. Thanks to hooks being composable by nature, a function will be able to combine other hooks to create a custom hook. By convention these functions will be prefixed by use.

The following defines a custom hook that creates a variable and logs its value on the console whenever the value changes:

ValueNotifier<T> useLoggedState<T>(BuildContext context, [T initialData]) {
  final result = useState<T>(initialData);
  useValueChanged(result.value, (_, __) {
    print(result.value);
  });
  return result;
}
  • A class

When a hook becomes too complex, it is possible to convert it into a class that extends Hook, which can then be used using Hook.use. As a class, the hook will look very similar to a State and have access to life-cycles and methods such as initHook, dispose and setState. It is usually a good practice to hide the class under a function as such:

Result useMyHook(BuildContext context) {
  return Hook.use(_MyHook());
}

The following defines a hook that prints the time a State has been alive.

class _TimeAlive<T> extends Hook<void> {
  const _TimeAlive();

  @override
  _TimeAliveState<T> createState() => _TimeAliveState<T>();
}

class _TimeAliveState<T> extends HookState<void, _TimeAlive<T>> {
  DateTime start;

  @override
  void initHook() {
    super.initHook();
    start = DateTime.now();
  }

  @override
  void build(BuildContext context) {
    // this hook doesn't create anything nor uses other hooks
  }

  @override
  void dispose() {
    print(DateTime.now().difference(start));
    super.dispose();
  }
}

Existing hooks

Flutter_hooks comes with a list of reusable hooks already provided. They are static methods free to use that includes:

  • useEffect

Useful to trigger side effects in a widget and dispose objects. It takes a callback and calls it immediately. That callback may optionally return a function, which will be called when the widget is disposed.

By default, the callback is called on every build, but it is possible to override that behavior by passing a list of objects as the second parameter. The callback will then be called only when something inside the list has changed.

The following call to useEffect subscribes to a Stream and cancel the subscription when the widget is disposed:

Stream stream;
useEffect(() {
    final subscribtion = stream.listen(print);
    // This will cancel the subscription when the widget is disposed
    // or if the callback is called again.
    return subscription.cancel;
  },
  // when the stream change, useEffect will call the callback again.
  [stream],
);
  • useState

Defines + watch a variable and whenever the value change, calls setState.

The following code uses useState to make a counter application:

class Counter extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final counter = useState(0);

    return GestureDetector(
      // automatically triggers a rebuild of Counter widget
      onTap: () => counter.value++,
      child: Text(counter.value.toString()),
    );
  }
}
  • useReducer

An alternative to useState for more complex states.

useReducer manages an read only state that can be updated by dispatching actions which are interpreted by a Reducer.

The following makes a counter app with both a "+1" and "-1" button:

class Counter extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final counter = useReducer(_counterReducer, initialState: 0);

    return Column(
      children: <Widget>[
        Text(counter.state.toString()),
        IconButton(
          icon: const Icon(Icons.add),
          onPressed: () => counter.dispatch('increment'),
        ),
        IconButton(
          icon: const Icon(Icons.remove),
          onPressed: () => counter.dispatch('decrement'),
        ),
      ],
    );
  }

  int _counterReducer(int state, String action) {
    switch (action) {
      case 'increment':
        return state + 1;
      case 'decrement':
        return state - 1;
      default:
        return state;
    }
  }
}
  • useMemoized

Takes a callback, calls it synchronously and returns its result. The result is then stored to that subsequent calls will return the same result without calling the callback.

By default, the callback is called only on the first build. But it is optionally possible to specify a list of objects as the second parameter. The callback will then be called again whenever something inside the list has changed.

The following sample make an http call and return the created Future. And if userId changes, a new call will be made:

String userId;
final Future<http.Response> response = useMemoized(() {
  return http.get('someUrl/$userId');
}, [userId]);
  • useValueChanged

Takes a value and a callback, and call the callback whenever the value changed. The callback can optionally return an object, which will be stored and returned as the result of useValueChanged.

The following example implicitly starts a tween animation whenever color changes:

AnimationController controller;
Color color;

final colorTween = useValueChanged(
    color,
    (Color oldColor, Animation<Color> oldAnimation) {
      return ColorTween(
        begin: oldAnimation?.value ?? oldColor,
        end: color,
      ).animate(controller..forward(from: 0));
    },
  ) ??
  AlwaysStoppedAnimation(color);
  • useAnimationController, useStreamController, useSingleTickerProvider

A set of hooks that handles the whole life-cycle of an object. These hooks will take care of both creating, disposing and updating the object.

They are the equivalent of both initState, dispose and didUpdateWidget for that specific object.

Duration duration;
AnimationController controller = useAnimationController(
  // duration is automatically updates when the widget is rebuilt with a different `duration`
  duration: duration,
);
  • useStream, useFuture, useAnimation, useValueListenable, useListenable

A set of hooks that subscribes to an object and calls setState accordingly.

Stream<int> stream;
// automatically rebuild the widget when a new value is pushed to the stream
AsyncSnapshot<int> snapshot = useStream(stream);
展开阅读全文

代码

的 Gitee 指数为
超过 的项目

评论 (0)

加载中
更多评论
暂无内容
发表于大前端专区
2020/01/17 12:39

Umi Hooks - 助力拥抱 React Hooks

这是蚂蚁金服内部技术分享的文字稿,本次分享主要介绍了为什么要用 Hooks,以及如何使用 Umi Hooks 提效。 - 开场 - 大家好,我叫尽龙,来自体验技术部。社区名称叫 brickspert,砖家的意思。 自从 React 推出 React Hooks 后,就开始尝试使用 Hooks,并逐渐喜欢上了它。目前,几乎 100% 的组件都是使用 Hooks 开发。经过大半年的实践,在 Hooks 使用方面沉淀了一些经验,很高兴今天有机会能分享给大家。 在分享开始之前,我想了...

0
0
发表了博客
2019/06/01 22:56

React-hooks

Hooks const {uerState}=React function Counter() { const [count, setCount] = useState(0);// 写在外层 return (<div> count:{count} <button onClick={_ => { setCount(count + 1) }}>加一 </button> <button onClick={_ => { setCount(count - 1) }}>减一 </button> </div>) } useState 参数是对象的时候 import {useSta...

0
0
发表了博客
2019/04/14 10:11

react--Hooks

React Hooks 是 React 16.7.0-alpha 版本推出的新特性 优点 无状态组件function不能带state 如果组件里需要用state,只能用class 没有this指向的问题 减少了层级嵌套, 代码简洁 不用调用各种生命周期 新建hooks文件夹 hooks/test1.js \ hooks/useState.js hooks/test1.js import React from 'react'; export default class Test1 extends React.Component{ constructor(props){ super(props); this.state = { ...

0
0
发表了博客
2013/05/23 21:18

git hooks

hooks是一些在$GIT_DIR/hooks目录的脚本,在被特定的事件(certain points)触发后被调用。当git init命令被调用后,一些常用的示例钩子文件被拷贝到新仓库的hooks目录中;但是默认这些钩子时不生效的。把.sample后缀去掉之后生效。 1. applypatch-msg GIT_DIR/hooks/applypatch-msg 当'git-am'命令执行时,这个钩子就被调用。 它只有一个参数:就是存有提交消息(commit log message)的文件的名字。如果钩子的执行结果是非零,那么...

1
6
发表了博客
01/13 21:00

hooks 与 animejs

hooks 与 animejs 本文写于 2020 年 1 月 13 日 animejs 是现如今非常不错的一个 js 动画库。我们将其与 React Hooks 融合,使它更方便的在 React 中使用。 最终效果: const Animate: React.FC = () => { const { animateTargetRef, animationRef } = useAnime({ translateX: 300, loop: true, duration: 2000, autoplay: false, }); useEffect(() => { setTimeout(() => { animationRef.current?.play?.(); }, 3000); }, [an...

0
0
发表于数据库专区
2016/09/04 18:43

Postgres Hooks

使用PostgreSQL中hook,可以不重新编译中断或者改变PG行为。 1.经常使用的Hook汇总 Hook ``` Initial release check_password_hook 9.0 ClientAuthentication_hook 9.1 ExecutorStart_hook 8.4 ExecutorRun_hook 8.4 ExecutorFinish 8.4 ExecutorEnd_hook 8.4 ExecutorCheckPerms_hook 9.1 ProcessUtility_hook 9.0 Hook Used in Initial release explain_get_index_name_hook 8.3 ExplainOneQuery_hook IndexAdvisor 8.3 fmgr...

0
1
发表了博客
2020/09/05 19:59

git hooks

小闫辛苦码了 1358 个字 文章可能占用您 4 分钟 欢迎关注「全栈技术精选」 今日分享:Ask yourself, who do you want to be? Figure out for yourself what makes you happy, no matter how crazy it may sound to other people. 如同 flask 框架的请求钩子一样,git 也有钩子 (hook) 的概念,下面就让我们一起来看看吧。更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』 1.概念 执行某些命令前后,统一进行的操作。...

0
0
发表了博客
2020/10/12 08:59

浅谈 Hooks

  如果你很熟悉 vue 与 react ,兴许你也觉得 vue3.0 抄袭了react,这项react 在不久前发布的新技术,在 vue3.0 中被重新搬上了舞台。也使它重新活跃在了人们的视野中,我技术不深,与大家分享我的见解和猜测。 useState 使用状态 const [n, setN] = React.useState(0) const [user, setUser] = React.useState({name: 'Jack', age: 18}) 注意事项: 无局部更新能力 如果state是一个对象,能否部分setState? 答案是不行,因为...

0
0
发表了博客
2018/11/08 18:14

理解 React Hooks

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由志航发表于云+社区专栏 TL;DR 一句话总结 React Hooks 就是在 react 函数组件中,也可以使用类组件(classes components)的 state 和 组件生命周期,而不需要在 mixin、 函数组件、HOC组件和 render props 之间来回切换,使得函数组件的功能更加实在,更加方便我们在业务中实现业务逻辑代码的分离和组件的复用。 本文将从以下几个方面介绍 hooks Hooks 在解决...

0
0
没有更多内容
加载失败,请刷新页面
点击加载更多
加载中
下一页
暂无内容
0 评论
4 收藏
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部