패키지, 저장소 및 앱 카테고리 검색

패키지 상세v0.1.12

virtual_dom

가상 DOM은 가상 DOM의 작고 가벼우며 낮은 수준의 구현입니다.

AndroidiOSWebmacOSWindowsLinux
30일 다운로드
22
좋아요
2
Pub 점수
150

패키지 설치

flutter pub add virtual_dom

패키지 개요

공개 정보를 바탕으로 생성한 선택 가이드입니다.

가상 DOM은 플러터 애플리케이션을 위한 가볍고 저수준의 가상 DOM 개념 구현입니다. 실제 DOM에 대한 직접적인 조작을 최소화함으로써, 특히 플러터 웹 환경에서 사용자 인터페이스를 효율적으로 업데이트할 수 있는 기초 메커니즘을 제공합니다. 이 패키지는 차등 비교 및 재조정 알고리즘을 통해 UI 상태 변경을 향상된 성능으로 관리할 수 있게 해줍니다. 범위는 작지만, 플러터 내에서 더 복잡한 UI 프레임워크를 구성하는 기본 블록 역할을 합니다.

사용 사례

  • Flutter에서 고성능 웹 UI 구축하기
  • 사용자 정의 렌더링 로직 구현
  • 웹 앱에서 상태 업데이트 최적화
  • Flutter 위에 가벼운 UI 프레임워크 만들기
  • DOM 조작 오버헤드 감소

주요 기능

  • 경량이고 최소한의 API
  • 효율적인 차이 비교 및 재조정
  • DOM 업데이트에 대한 저수준 제어
  • 다중 플랫폼 지원 (웹, 안드로이드, iOS, macOS, Windows, 리눅스)
  • 더 큰 UI 시스템에 통합하기 위해 설계됨

적합한 대상

  • 사용자 정의 UI 프레임워크를 구축하는 개발자
  • Flutter Web 성능 최적화
  • 고급 UI 상태 관리
  • 저수준 DOM 조작이 필요합니다
  • 최소 종속성 요구 프로젝트

고려 사항

  • 추가적인 추상화 없이 일반적인 UI 개발에 적합하지 않음
  • 고급 패턴의 수동 구현이 필요합니다
  • 제한된 문서 및 커뮤니티 지원
  • 독립적인 솔루션보다는 기반으로 사용하는 것이 가장 좋습니다
  • 일반적인 Flutter 앱 개발에는 필요하지 않을 수 있습니다

원본 README

연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.

virtual_dom

The Virtual DOM is a small, lightweight, low-level implementation of the Virtual DOM.

Version: 0.1.12

What is this software and what is it not?

This software does not contain any ready-made user interface elements.
This software is the basis and is intended for the implementation of user interface elements (application components) and client web applications from scratch.

What is a virtual DOM?

Virtualization is a rather complex implementation, despite the small size, of the interaction of various virtual nodes, possibly in different states, the renderer, the mechanism for tracking their state, the procedures for their identification, comparison and decision-making about their reuse by rebuilding.
The virtual DOM is a tree of virtual nodes that is used to build the DOM tree.
The virtual tree supports partial updating of virtual nodes.
In the case of applying the simplest operations (such as insert or remove) on the tree, only part of the DOM tree is updated.
This causes the DOM tree to update very quickly.
This is true when unique keys are used to identify virtual nodes of the same type.
The use of non-unique keys is not allowed and will result in an exception.
Keys need to be unique only at the same level (that is, at the parent node level).
Simultaneous use of virtual nodes with and without keys is allowed.

What is the purpose of virtual DOM?

Using the virtual DOM, can be developed fast client-side browser applications such as admin panels, e-commerce applications, chat rooms and the like.
Virtual DOM nodes are almost equivalent to DOM nodes.
Those developers who are familiar with HTML and CSS can easily use virtual nodes.
But since the use of only virtual nodes is not a sufficiently functional solution, virtual components are provided for full-fledged work.
Virtual components are, in fact, the same elements of the virtual tree as virtual nodes, but they have significant differences.
Virtual components do not have equivalent to DOM nodes.
They should produce content (as a result of rendering) that consists of virtual nodes and/or virtual components.
To be more precise, there are two kinds of components - virtual components and application components.
A virtual component wraps an application component.
Component usage is the usage of application components.

Virtual node keys, component keys and component effect keys

Virtual node keys must be unique within the parent.
The main (and most logical) purpose of these keys is to index the elements of collections (similar to database primary keys).
This is required in order to provide a quick computation of the presence of changes in the tree (performing only the key comparison operation).

Component keys can be used to identify instances of components of the same type.

Component effect keys can be used to indicate that the component requires rerendering if the previous value of this key differs from the current one. Usually the values for the effect key are taken from the current arguments which effect the rerendering process.

Helper functions

Helper function means some global function to simplify the use of this software.
This is a pseudo concepts and is not a convention.
Currently there are several helper functions:

  • el
  • h
  • mount
  • styles
  • vHtml
  • vKey

The h function helps to declare virtual elements.
Using this function is optional and you can write your own similar function (a hundred times better than this one).
But, in any case, the author of this software considers this function to be at least a little, but useful, and therefore considered it possible to include it in this software.

Example:

return h('div', [
  h('div', 'Counter: $count'),
  h('button', 'Click', {
    'click': (Event event) {
      setCount(count + 1);
    }
  }),
]);

The mount function helps to mount virtual components to browser DOM elements.
It is usually used at the beginning of the program start.

Example:

final app = document.getElementById('app')!;
mount(app, App());

The styles function helps to create HTML style attribute.

Example:

final style = styles({
  'background-color': 'black',
  'color': 'red',
  'display': 'block',
  'margin': '8px',
  'padding': '8px',
});
return h(
  'div',
  {'style': style},
  h('div', [for (final line in lines) h('div', line)]),
);

The vHtml function helps to create VHtml node.
In fact, it only calls the constructor, but it plays an important role - it hides implementation details. Since it is not recommended to use virtual nodes directly in the Component.render() method.

Example:

Object render() {
  final style = styles({
   'color': color,
  });
  return h('div', {
    'style': style
  }, [
    vHtml('style', _style),
    h('div', {
      'class': 'spinner'
    }, [
      h('div', {'class': 'bounce1'}),
      h('div', {'class': 'bounce2'}),
      h('div', {'class': 'bounce3'}),
    ]),
   ])
    ..useShadowRoot();
}

The vKey function helps to add a key (most often a collection element key) to a virtual node.

Example:

Object render() {
  final changeState = State.change();
  final application = App.get();
  Listener.use(application.windows, changeState);
  final uiFactory = application.uiFactory;
  final list = [];
  final windows = application.windows.value;
  for (final window in windows.values) {
    final windowListItem = uiFactory.createWindowListItem(window);
    final item = h('div', windowListItem);
    list.add(vKey(window.uri, item));
  }
   return h('div', list);
}

TODO: Add explanation for other helper functions

Application component

An application component (or, more simply, a component) is a component that represents the part of an application that is responsible for rendering user interface elements.

Example:

import 'dart:html';

import 'package:virtual_dom/features.dart';
import 'package:virtual_dom/virtual_dom.dart';

void main(List<String> args) {
  final app = document.getElementById('app')!;
  mount(app, Counter());
}

class Counter extends Component {
  @override
  Object render() {
    final count = State.get('count', () => 0);
    final setCount = State.set<int>('count');
    return h('div', [
      h('div', 'Counter: $count'),
      h('button', 'Click', {
        'click': (Event event) {
          setCount(count + 1);
        }
      }),
    ]);
  }
}

Warning regarding manual mounting.
Since this mount method is a manual mount, when unmounting (if required) you must explicitly call the dispose() method on the component (if required).
The reason is that the top components do not have a parent. Only the parent calls the dispose() method on the child node, or the renderer called by the parent.
In most cases, this is not required, just close the page tab in the browser.

Features

The component itself is not very functional, it is only responsible for returning what can be rendered.
Special classes are intended to give the component some features.
Again, this is just a pseudo concept, not a convention.
At the moment there are several classes that implement the features:

  • InheritedProperty
  • InheritedValue
  • Init
  • Listener
  • State
  • ValueListener
  • useErrorReport
  • useValueWatcher
  • useWatcher

And, of course, nothing prevents from independently implementing other features to expand the functionality.
If you think that it is difficult, then it is not so - it is very simple (it will be enough to look at the source code of these classes).

The InheritedValue feature allows to define some Listenable value (specifying its name and type) in the parent component, which will be available to child elements by its name.
When the value is changed, all components using it will be automatically rendered.

There is nothing to prevent this feature from being used with values that do not need to be changed to make that value available to other child elements (although for this, perhaps, it would be worth creating another feature).

Example:

// Parent
final value = InheritedValue.add('value', () => 0);

// Child
final value = InheritedValue.get<int>('value');

The Init feature allows to define init and dispose (dispose is optional) handlers that will be executed exactly once, at the right moment.

Example:

Init.use(() {
  _count++;
  return () {
    _count--;
  };
});

The Listener feature allows to automatically add and remove a listener to a ChangeNotifier with specified action.
This is a feature, which will do everything itself - add a listener (during initialization) and remove it (when disposing).

Example:

final changeState = State.change();
Listener.use(_value, changeState);

The State feature allows to define a value that will be stored in the component internal context until component is disposed of.
Also, this feature allows to get a function to change this value.
Any change to this value will render the component.
And, of course, the function definition is available to unconditionally render the component.

final step = State.get('step', () => 0);
final setStep = State.set<int>('step');
final changeState = State.change();

The ValueListener feature allows to automatically add and remove a listener to a ValueNotifier with specified action.
This is a feature, which will do everything itself - add a listener (during initialization) and remove it (when disposing).

Example:

final val = State.get('val', () => 0);
final setVal = State.set<int>('val');
ValueListener.use(_value, setVal);

TODO: Add explanation for other features

Using Shadow DOM

Using a Shadow DOM is not much more complicated than using a regular DOM.
In other words, everything is simple.
Shadow DOM can be used with the VElement virtual node.
In order to indicate the need to use the Shadow DOM, it is enough to configure such a virtual node before using it.
For these purposes, the VElement.useShadowRoot() method is used.

Example:

class _Component1 extends Component {
  final String color;

  final String text;

  _Component1(this.text, this.color) : super(key: '$text#$color');

  @override
  Object render() {
    final style = '''
.specialColor {
  color: $color
}''';

    return h('div', [
      vHtml('style', style),
      h('div', {'class': 'specialColor'}, text),
    ])
      ..useShadowRoot();
  }
}

The ..useShadowRoot() in this case will be applied to the returned element (in this case, the topmost h('div')) and, as a result, the same element will be returned (and configured to use Shadow Root).
Configuring an already used node (already placed in the tree) leads to incorrect operation.
But this does not mean that it is impossible to return a new, any node. All these states are tracked, recognized as different, and everything will work correctly.

Render error reporting

When errors occur during rendering, the best way in this situation is to report this error.
For these purposes, virtual nodes have special procedures that are able to render safely.
Safely, in this case means using try/catch statements.
But what to do with the information about these errors?
There is a special class ErrorReport for this purpose.
The virtual component contains a field ValueNotifier<ErrorReport?>? errorReport.
By default, this field is not initialized, which means that this virtual component does not take responsibility for error reporting.
But as soon as this value is set (in some component), it will mean that this component takes over the task of displaying error messages.

It would seem that everything is simple, but not everything is so simple.
What happens if a rendering error occurs in this component, which itself should display error messages?
Since this component will no longer be re-rendered, this will result in no error report being displayed.
In this case, it would be more correct to pass the value ValueNotifier<ErrorReport?> to the child component.
Because the principles of virtualization do not prevent children from "living their own life" (being rendered at least once) they will continue to exist and function even in a "broken" parent.
And in this case, they will be quite capable of displaying error reports.

All the parent has to do is initialize the VComponent.errorReport field and render the child element to which this value will be passed.
The child element (component) should track changes to this value (watch) and output the changes (error reports).

Example can be found here: https://github.com/mezoni/virtual_dom/blob/master/example/example.dart

This is not the only possible way to accomplish this task, but it is a fairly optimal way.

The nesting of components that take responsibility for displaying error messages can be any, and in case of an error, the closest one will be used.
The VNode.findErrorReport() method is responsible for finding the closest or only component.
It checks the current component and then iterates through all the parents up to the very top.

Global error reporting

It is not a good idea to add a global ErrorReport instance, but sometimes it is necessary if you want to know where the error is.
The class ErrorReport has a static field ValueNotifier<ErrorReport?> global which can be used under any circumstances.
But, as always, not everything is so simple here. The whole problem is that errors that occur in UI elements and in all other places cannot be considered as errors that can be caught in one way.
Errors that occur in UI elements are caught by the rendering procedures. All other errors are not caught by the rendering procedures.
This means that need to take some special action for this.
One possible action is to create a global ErrorReport for this purpose.
Keep in mind that ErrorReport by itself does nothing but store information about the error.
That is, it is necessary to create a UI component to display an error report.
As a result, two possible solutions for these purposes.

  • Global error report for all cases
  • Global error report for all cases, local error reports for UI

Example of using global error reporting outside of UI procedures:

Timer.run() {
  ErrorReport.run(() {
    try 'Some error';
  });
});


Timer.run() async {
  return ErrorReport.runAsync(() async {
    await f();
    try 'Some error';
  });
});

카테고리

소스 저장소

이 패키지를 사용하는 프로젝트

아직 데이터가 없습니다.