Introduction of Fleur - It's a new Flux framework 
Tags: react, javascript, typescript
Published: 2019-05-22 13:57:05

---
title: Introduction of Fleur - It's a new Flux framework 
published: true
description: Introduction for new Flux framework which make by self.
cover_image: https://thepracticaldev.s3.amazonaws.com/i/x5s82l672o2pdxu5rc3j.png
tags: react,javascript,typescript
---

_It's self-translated article. Source: https://inside.pixiv.blog/ragg/7050_

Hello everyone! I'm [Hanakla](https://github.com/hanak1a_) who is a front-end engineer at [VRoid Hub at pixiv](https://hub.vroid.com/) 🙌

This time, I will introduce the design and usage of [“Fleur”(npmjs:@fluer/fleur)](https://github.com/ra-gg/fleur), a flux framework I have developed for about a year!

# table of contents

- What kind of framework?
- Usage
- Question
- Finally

# What kind of framework?

It referred to [Fluxible](https://fluxible.io/), which is adopted in [pixiv Sketch](https://sketch.pixiv.net) , I made a full scratch with TypeScript, focusing on two points: "Writing ease" and "Adopting modern features". (I think that Fluxible is the most "complete" framework I know ♨)

Although it has not been adopted in large-scale production of Fleur, it has been developed by adjusting API and performance for one year in a performance-sensitive [Web-based VFX application product called "Delir"](https://github.com/ra-gg/Delir).

## Feature

- Server-side rendering (SSR) ready
  - Integration tests such as [express + Fleur SSR validation test](https://github.com/ra-gg/fleur/blob/master/packages/fleur-react/src/ssr.spec.tsx) and [Todo MVC SSR implementation](https://translate.googleusercontent.com/translate_c?depth=1&rurl=translate.google.com&sl=ja&sp=nmt4&tl=en&u=https://github.com/ra-gg/fleur/tree/master/packages/example-todomvc&xid=17259,15700023,15700186,15700190,15700256,15700259&usg=ALkJrhgr7z2SOE68p8jOuNOcMtiHfYgd6A)
- React Hooks compatible (`useFleurContext()`, `useStore()` etc.)
- Comfortable to writing the code
- Supports asynchronous processing as standard
- immer.js Built-in Store
- SSR compatible router
  - Design considering API fetch
Supports delayed component loading by Dynamic import (no code conversion required)
- Performance approaching redux + react-redux
  - Some benchmarks may be faster than react-redux
- Redux DevTools compatibility
  - Not compatible with Skip action

<!--
## How was it born

Fleur was born looking for a more robust destination for Delir, which was implemented in FluxUtils .

From the design point of view, I thought that Fluxible was the best (*) as a migration destination, but there was no type definition of Fluxible, and because it was an API incompatible with type inference for performing type definition on its own, I made up my mind, "OK, I made it self." (I actually wrote the type definition of Fluxible when I developed pixiv's "Goods Selling Feature", but it was awfully difficult🤢)

After that, based on the front-end and BFF implementation in each pixiv product, the feeling of use in Delir, and React Hooks, I refine the design in the direction of what kind of API can write code that is simpler and more robust and can be used in production. went.

In particular, in Delir, the product from which Fleur was born, "Rendering of video" and "UI processing" coexist on the main JavaScript thread, so tuning is done so that there is no problem in terms of performance.

※ aside

I also considered using "Redux + react-redux + redux-thunk" for the configuration of the Delir, but at that time I had not yet recognized immer.js, and it was necessary to secure the independence of Delir's engine side API Because of that, Fluxible was supposed to be the best framework that could be adopted without changing the API of the engine. (The project schema depended on the nested class structure and it was difficult to change to an immutable object group)

It seems that recently immer.js has started to hold prototypes of modified objects, so there may be different ways now. (But don't use JSON or more objects in the front end for State. Mostly View will be a rimbo)

-->

### In search of the easier to write and robust Flux framework.

First let's look at the definition of Action / Operations / Store in Fleur.

```tsx
// Action
import { actions, action } from '@fleur/fleur'

export const CounterActions = actions('CounterAction', {
    increase: action<{ amount: number }>(),
    decrease: action<{ amount: number }>(),
})

// Operations
import { operations } from '@fleur/fleur'
import { CounterActions } from './actions.ts'

export const CounterOps = operations({
  async increase({ dispatch }, amount: number) {
    dispatch(CounterActions.increase, { amount })
  },

  async decrease({ dispatch }, amount: number) {
    dispatch(CounterActions.decrease, { amount })
  }
})

// Store
import { listen, Store } from '@fleur/fleur'
import { CounterActions } from './actions.ts'

type State = {
  count: number
}
 

export class CountStore extends Store<State> {
    public state: State = { count: 0 }

    private handleIncrease = listen(CounterActions.increase, (payload) => {
        this.updateWith(draft => draft.count += payload.amount)
    })

    private handleDecrease = listen(CounterActions.decrease, (payload) => {
        this.updateWith(draft => draft.count -= payload.amount)
    })

    public get count() {
        return this.state.count
    }
}
```

Since the definition of Action itself and its type definition are integrated, the process of not having scattered descriptions and having to write multiple code for defining one Action is reduced. The definition of Action is called an `Action Identifier` inside Fleur.

Operations supports asynchronous processing from the beginning. Therefore, there is no need to choose thunk or saga.

Store is characterized by the `listen()` and `.updateWith()`. `listen()` infers the argument type of the action handler based on the Action Identifier. In Fluxible, the action type and the name of its handler method were specified in the property named handlers, but as a result of considering code that is simpler and type inference-friendly, it is such an API. (The function `reducerStore()` can be created with reference to [mizchi/hard-reducer](https://github.com/mizchi/hard-reducer).)


### Also wanted a router designed based on the real scene.

Fleur's standard router `@fleur/route-store-dom` is designed based on the production code of SSR services such as pixiv Sketch and VRoid Hub.

To the extent that I have investigated, the router libraries that currently exist around React seem to have the problem that "the best practices for API requests do not exist" in general. Even in the VRoid Hub using the `react-router`, its implementation is quite dirty. In that respect, Fluxible's `fluxible-router` had an API close to the ideal solution, so Fleur followed it.

The fact that having a process that can be out of sync in the React world, which is supposed to be synchronous processing, is not yet compatible at present, and I think that even if React Suspense is included, it may not be possible for SSR to have a painful state for a while. 😔 😔 (There is also a theory that SSR will not be required by [Googlebot engine update](https://webmasters.googleblog.com/2019/05/the-new-evergreen-googlebot.html))

As a feature `fluxible-router` doesn't have, support for dynamic import of components is implemented in Fleur. In existing router libraries, it is react-loadable to work hard with react-loadable , or use a large-scale method such as separating SSR and CSR code using transform by babel etc. However, in Fleur, routing is performed outside the React life cycle. It is possible to cope with dynamic import without any code transform.

```tsx
import { createRouteStore } from '@fleur/route-store-dom'

export const Router = createRouteStore({
  userShow: {
    path: '/user/:id',
    // Can be API fetching before page load.
    action: ({ executeOperation }, route) =>
      Promise.all([
        executeOperation(fetchUser, { id: route.param.id }),
      ]),
    // And support dynamic import.
    handler: () => import('./routes/User'),
  },
})
```

# Usage

Fleur requires at least four elements: Actions, Stores, Operations, and View to create an application. It feels like a to many, but I think that if you write a medium-scale app, you can pay relatively well. (It looks like Redux's Action Creator is separated into Actions and Operations.)

![](https://thepracticaldev.s3.amazonaws.com/i/cbph6p21m05hkdgcnpmp.png)


The recommended directory structure looks like this:

```
app/ 
  └ components/
  └ domains/
    └ Entity
    └ actions.ts
    └ operations.ts
    └ store.ts 
```

From here on, we will add sample code in order to explain the flow.

## Actions

he reality of Action in Fleur is just a type store, a function that does nothing. `action()` function accepts the payload type as an argument and returns a function that can not actually be called.

Within Fleur, the function instance generated at this time is called the `Action Identifier`, and at dispatch time it fires the Store listener with strict equivalence to the function instance. (It is a JavaScript trick)

```tsx
// Actions definition (actions.ts)
import { action } from '@fleur/fleur'

export const CounterActions = actions('CounterAction', {
    increase: action<{ amount: number }>(),
    decrease: action<{ amount: number }>(),
})
```

## Store

In Store, use the `listen()` function to specify which action to handle.

```tsx
// store.ts
import { listen, Store } from '@fleur/fleur'
import { CounterActions } from './actions.ts'

type State = {
  count: number
}

export class CountStore extends Store<State> {
    public state: State = { count: 0 }

    private handleIncrease = listen(CounterActions.increase, (payload) => {
        this.updateWith(draft => draft.count += payload.amount)
    })

    private handleDecrease = listen(CounterActions.decrease, (payload) => {
        this.updateWith(draft => draft.count -= payload.amount)
    })

    public get count() {
        return this.state.count
    }
}
```

There are two features of Store.

1. The above-mentioned `Action Identifier` infers the `payload` type.
2. `this.updateWith()`. Since this method is actually a wrapper for immer.js, updating the state is immutable even if you mutate the draft. The return value is ignored by Fleur, so there is no need to add braces like `draft => { ... }`.

 `.updateWith()` updates the status and notifies the View side of the change, but this update notification is buffered using `requestAnimationFrame` (*1, *2), so multiple times within a single handler `.updateWith()` will also minimize blocking to the UI.

Refer to the following code for details on how the listen function associates an action with a handler.

Contents of `listen()`: [fleur/src/Store.ts#L12](https://github.com/ra-gg/fleur/blob/4c452cd/packages/fleur/src/Store.ts#L12)
Linking to Action: [fleur/src/AppContext.ts#L116](https://github.com/ra-gg/fleur/blob/4c452cd/packages/fleur/src/AppContext.ts#L116)


*1 Even if the status of multiple stores changes in one dispatch, they will be grouped and buffered.

Implementation of Store#updateWith: [fleur/src/Store.ts#L47](https://github.com/ra-gg/fleur/blob/4c452cd/packages/fleur/src/Store.ts#L47)
Change Notification Buffering: [fleur/src/StoreContext.ts#L12](https://github.com/ra-gg/fleur/blob/4c452cd/packages/fleur/src/StoreContext.ts#L12)

*2 SSR does not perform buffering but processes synchronously.

## Operations

This is a name unique in Fleur, but it is a layer that causes communication with the API and side effects. It corresponds to `redux-thunk` etc. Entity normalization is also easy to handle if it is done in this layer by cutting normalization processing into functions or using [`normalizr`](https://github.com/paularmstrong/normalizr).


```tsx
// operations.ts
import { operations } from '@fleur/fleur'
import { CounterActions } from './actions.ts'

export const CounterOps = operations({
  async increase(context, amount: number) {
    context.dispatch(CounterActions.increase, { amount })
  },

  async decrease(context, amount: number) {
    context.dispatch(CounterActions.decrease, { amount })
  }
})
```

`{dispatch, getStore}` is passed in the `context` of each method, and it is also possible to refer to the state of the Store from within the operation. For example, it is useful in the case where "The authentication information of the user in the Store is required when making API request". `operations()` function itself is just for type inference and does nothing in particular.

Operations support Promise as standard, so you can use async / await sparingly.

## View

Next, connect these to View.

```tsx
// App.tsx
import React, { useCallback } from 'react'
import { useFleurContext, useStore } from '@fleur/fleur-react'
import { CounterOps } from './operations'
import { CountStore } from './store'
 
export const App = () => {
  const { executeOperation } = useFleurContext()
 
  const { count } = useStore([CountStore], getStore => ({
    count: getStore(CountStore).getCount(),
  }))
 
  const handleCountClick = useCallback(() => {
    executeOperation(CounterOps.increase, 10)
  }, [])
 
  return <div onClick={handleCountClick}>{count}</div>
}
```

View uses `useStore()` and `useFleurContext()`.

`useStore()` the Store class to be monitored in the first argument to useStore and the function to retrieve the state from the Store in the second argument, component update will be performed when there is a Store update. (This one is similar to react-redux's `connect()`)

`useFleurContext()` is hooks that returns `{ getStore, executeOperation }`. When executing some operation depending on the user action, pass the operation you want to execute and its arguments to `executeOperation`. `getStore` is useful for scenes that "do not affect the appearance but want the state of the Store in the callback". (It should basically be done within Operations)

Finally, writing the launch part of the application completes the entire flow!

```tsx
// client-entry.tsx
import Fleur from '@fleur/fleur'
import { FleurContext } from '@fleur/fleur-react'
import React from 'react'
import ReactDOM from 'react-dom'

import AppRoot from './App'
import { CountStore } from './store'
 
const app = new Fleur({ 
  stores: [ CountStore ] 
})

const context = app.createContext()

window.addEventListener('DOMContentLoaded', () => {
  const root = document.querySelector('#root')

  ReactDOM.render(
    <FleurContext value={context}>
      <AppRoot />
    </FleurContext>
  , root)
})
```

# Question
## Router?

For a sample Router, see the [README for @fleur/route-store-dom](https://github.com/ra-gg/fleur/tree/master/packages/route-store-dom).

What is the sample of SSR?
I will omit it in this article, but please take a look at this sample.

- [route-store-dom/README.md](https://github.com/ra-gg/fleur/blob/master/packages/route-store-dom/README.md)
- [SSR implementation of TodoMVC](https://github.com/ra-gg/fleur/tree/master/packages/example-todomvc)

Does memory leak occur when operating in a large scale environment? It is hoped that you can give us feedback as there are not enough tests for that.

## Performance, what about numerically?

It feels like the benchmark of Fleur vs Floible vs react-redux on Travis CI.

![](https://thepracticaldev.s3.amazonaws.com/i/15zu8xoi5bkduwlzfnwp.png)

As a result of optimizing the performance, it was overwhelmingly faster than Fluxible, and the performance was so thin or overtaking against react-redux.

There are three main optimizations that Fleur is doing internally:

Batch notification of updateWith () calls in a short time in the Store
Batch notification of multiple dispatch in a short time across the Store
Component-side bounce of change notifications from multiple Stores listening
Anyway, I made it to optimize in the feeling of "It is heavy when Re-rendering with React is raised!!! Another is nothing!!!"

# Finally

I've just released a major version, and I think that there are still improvements since there is little knowledge about React Suspense and GraphQL, but I think that I can now cope with most problems.

If you feel like "Why did the 21st century become reluctant, but we are writing routings that we didn't have to write in the 20th century anymore?" You can feel good 🔫☺

Thanks for reading! cheers~~