Flutter is an efficient cross-platform UI framework. Compared to React Native and WEEX, flutter does not need to be converted to native controls and does not need to be bridged by JScore as a middle layer. It can render UI directly through the SKIA engine, which has many advantages

This article is about flutter and responsive frameworks from the perspective of a front-end developer

Responsive frame



This is the basic structure of an MVVM framework

Basically, the MVVM framework is powerful for any scenario that requires rendering the UI

Because essentially it completely decouples the data from the view

mvc/p



Going back to the front-end MVC/P framework, these are some pretty pure data/view separation frameworks

It is highly modular, but not solvableSynchronization between data and viewsThe problem of

Or that the synchronization should be left to the platform (browser)

It wasn’t until front-end engineering became popular that various MVVM frameworks emerged

Their general principles are as follows:

This is what happens in Flutter:

Component nature

Ok, back to Flutter, this is Hello World in Flutter

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          child: new Text('Hello World'),),),); }}Copy the code

Most of the stuff in a flutter is a widget. To put it simply, it can be a front-end Component. It can exist either individually or nested as a parent Component.

Back in the days of jQuery, when jQuery plugins were very popular, you know, date plugins, pagination plugins and so on and each of these plugins could output a DOM that did a particular thing so is it a component? Yes.

Back in the present day, we also write various Component/ widgets to implement various functions

In layman’s terms, components are “functions that generate reusable UIs” except that they have different outputs — one for the real DOM and one for the virtual VNode

In MVVM, the VIEWMODEL automatically synchronizes the view with the data, and VNode is a big part of that in terms of minimizing changes to the view, and also enabling the framework to be cross-platform and so forth throughout the life cycle of the component

cross-platform

Based on VNode, many MVVM frameworks have cross-platform capabilities because they just need to generate VNode code for the corresponding platform. Weex and Rn do the same

Flutter, however, performs UI rendering directly through the SKIA engine. The advantage of Flutter is that it does not need jScore as an intermediate layer to bridge, resulting in less difference in UI performance between platforms and a significant performance improvement

Flutter for Web re-implements the DART: UI library, replacing the Skia engine binding used on mobile with code for DOM and Canvas.Copy the code

Component types

In front-end development, we will use both normal components and functional components. Normal components have their own state and life cycle, while functional components simply output the specified VNode and cannot change their state in flutter, especially

statelessWidget

Let’s look at a simple Flutter demo

class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});

  final int count;

  @override
  Widget build(BuildContext context) {
    return new Text('Count: $count'); }}class CounterIncrementor extends StatelessWidget {
  CounterIncrementor({this.onPressed});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return new RaisedButton(
      onPressed: onPressed,
      child: new Text('Increment')); }}Copy the code

Flutter encourages the use of stateless components. First, for performance reasons, functional components have obvious advantages in generation and diff. Second, more importantly, we need to get into the habit of looking at our code

stateFullWidget

Let’s take a look at this demo

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => new _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(children: <Widget>[
      new CounterIncrementor(onPressed: _increment),
      newCounterDisplay(count: _counter), ]); }}Copy the code

React :State,setState,build, react

We should always remember that a Widget is a temporary object that may be called multiple times to build applications in its current State. It is only a configuration. However, the State object should not change between multiple calls to Build (). Allow them to remember states

A call to setState tells the Flutter framework that a state change has caused the application to rerun the Build method so that the application can reflect the change. Build is the equivalent of the JXS or Vue template returned by React.

Now that you understand this, you can move on to the interaction between components

State management

All communication between MVVM framework components follows this logic:

A parent class passes a value to a child class through an attribute and a child class passes a value to its parent class through an eventCopy the code

This is the clearest direction of the data flow. We can clearly see how the data changes, and this is certainly the case in flutter:

class Parent extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    returnParentState(); }}class ParentState extends State<Parent> {
  String data = "No";
  Sring props = "some thing";
  
  void onChanged(val){
    setState(() {
      data = val;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Column(
        children: <Widget>[
          new Container(
            child: new Column(
              children: <Widget>[
                new Child(props: props,callBack: (value)=>onChanged(value)),
                new Text('from child : $data'[[() [[() [() [() [() [() }}Copy the code

But in reality, business is never that simple, a lot of data needs to be shared and in MVVM it’s a case of a data change, associated views need to be reconstructed which is a natural fit for the observer model Redux, Vuex and all of these state management tools are based on that and to some extent EventBus, I won’t repeat it here

InheritedWidget

InheritedWidget is an important functional component of Flutter that provides a way for data to be passed and shared from top to bottom in the widget tree

In essence, it adds the current Widget as a dependency, notificating all dependencies to update it when its data changes. Using it is cumbersome. Let’s look at a demo of the ChangeNotifierProvider based on the inheritedWidget implementation

 runApp(
    // Provide the model to all widgets within the app. We're using
    // ChangeNotifierProvider because that's a simple way to rebuild
    // widgets when a model changes. We could also just use
    // Provider, but then we would have to listen to Counter ourselves.
    //
    // Read Provider's docs to learn about all the available providers.
    ChangeNotifierProvider(
      // Initialize the model in the builder. That way, Provider
      // can own Counter's lifecycle, making sure to call `dispose`
      // when not needed anymore.
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}
class Counter with ChangeNotifier {
  int value = 0;

  void increment() {
    value += 1; notifyListeners(); }}return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
         // Use data
            Consumer(
              builder: (context, counter, child) => Text(
                '${counter.value}',
                style: Theme.of(context).textTheme.display1,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // Operate data
        onPressed: () =>
            Provider.of(context, listen: false).increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );

Copy the code

We use the ChangeNotifierProvider to register the observed, use the Consumer to register the observer provider.of (context) method to get the observed, process the data logic and notify the observer to rerender

Of course, you can do this without the hassle of using a direct reference if you really know what you’re doing and only use it in a specific context (if you’re using it in a normal component, take a look at the code, it can mess up the flow).


      ParentState _p = context.findAncestorWidgetOfExactType<ParentState>().data;
      _p.setState(() {
          _p.data = "some thing";
      });

      globalState.setState(() {
              globalState.name = "some thing";
      });
Copy the code

You can either get the parent from the context or you can just assign the parent’s State to a global variable, and you can change the State directly, and you can update it without any problems of course that’s not recommended

reusability

Once we get comfortable with it, we start to see new problems. We might write a lot of components, but some of them can be shared. How can we reuse them

It’s easy to think of hybrid components and higher-order components and hooks that can be implemented in Flutter

mixin

This mixing in flutter is naturally supported by the DART language

mixin _dataStateMixin < T extends StatefulWidget> on State<T> {
  var _data = 0;
  void_incrementCounter() { setState(() { _data++; }); }}class _CounterState extends State<CounterPage> with _dataStateMixin {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Counter:',
            ),
            Text(
              '$_counter',
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}Copy the code

We can easily create a hybrid class in Dart to pull common logic out and reuse it in mixins (note that errors will be reported if the same properties are present)

hoc

As for higher-order components, this is a bit of a shame because we know that the DART used in Flutter is modified to remove reflection so that we can’t create components dynamically, and higher-order components are not possible for the time being

However, if we ignore the condition of dynamic creation, we can use Builder to reuse some logic. Just like HOC, it also has some disadvantages: 1. The state logic of parallel relationships is grouped into parent-child relationships. 2. Multiple levels of nesting can be very difficult to read

hooks

If both approaches are a little uncomfortable, try using Hooks that encapsulate common logic for different life stages within our component

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() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    returnContainer(); }}Copy the code

This is a typical scenario where every component that uses AnimationController inevitably writes some logic over and over again during the lifecycle after hooks are used

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

  final Duration duration;

  @override
  Widget build(BuildContext context) {
    final controller = useAnimationController(duration: duration);
    returnContainer(); }}Copy the code

More fine-grained logic organization and reuse is evident (we can use multiple hooks in the same Widget)

It is a new way of organizing. For example, useState reorganizes the way we useState, useMemoized initializes and caches things, custom hooks, etc. Github.com/rrousselGit…

Of course, hooks are not a panacea, they should only be a way to organize your code logic

conclusion

Today I haven’t talked too much about the FLUTTER API, layout, etc. I just want to share my own ideas from the aspects of components and frameworks combined with some front-end things. I hope I can give some inspiration to front-end developers