头图

Hello, plume2.

import React, { Component } from 'react'

import {Actor,Store,StoreProvider,Relax,ViewAction} from 'plume2'


//MapReduce  
class HelloActor extends Actor{
    defaultState(){
        return {text:"Hello,plume2"}
    }
}

//reactive ui event 反应式ui事件
class AppViewAction extends ViewAction{
    sayHello = (text:any) =>{
        this.store.dispatch('say:hello',text)
    }
}


// Single Data Source 单一数据源
class AppStore extends Store{
    //bind data transform 绑定数据转换
    bindActor(){
        //after plume2   directly pass Actor class 直接通过Actor类
        return [HelloActor]
    }

    //bind ui event 绑定ui事件
    bindViewAction(){
        return {
            AppViewAction
        }
    }
}

//Auto compute relaxProps 自动计算打开Props
@Relax 
 class Text extends React.Component{
    static relaxProps = {
        //auto injected by store.state().get('text')  由store.state().get('text')自动注入
        text:"text",
        //auto injected by store's bindViewAction 由store的bindViewAction自动注入
        ViewAction:'viewAction'
    }
    _handleClick = () =>{
        const {text,viewAction} = this.props.relaxProps
        viewAction.AppViewAction.sayHello(text)
    }
    render(){
        const {text,viewAction} = this.props.relaxProps;
        return (
            <div onClick={this._handleClick}>{text}</div>
        )
    }
}

@StoreProvider(AppStore) //应用程序入口
export default class Index extends Component {
    render() {
        return (
            <div>
                <Text/>
            </div>
        )
    }
}

image.png

API Actor

Actor calculation model, an independent calculation unit, whose main function is to transform our state data

import { Actor,Action} from 'plume2'

// 是的  这就是一个Actor简单世界
class HelloActor extends Actor{
    // 领域的初始数据  该数据会被自动的转换为immutable
    defaultState(){
        // 返回的对象会被自动的转化为immutable
        // 除非有特殊数据结构如(set OrderedMap之类)
        //不需要特殊指定immutable数据结构
        return {text:'hello plume2'}
    }
    //**  通过@Action来建立store的dispatch和actor的handler(处理程序)之间的关联
    // API规范
    // @param state actor上一次的immutable状态
    // @param text  store dispatch的参数值  尽量保持单值设计
    @Action('change:text')
    change(state,text){
        // immutable api
        return state.set('text',text)
    }

}

Store

What is Store?
Store Our data state container center manages the life cycle of the entire app's data
We adhere to the idea of single data source (single data source), and maintain a complete business and UI state in the store
The main responsibilities of Stor:
1 Aggregate actors
2 dispatch actor (single dispatch transaction dispatch)
3 Calculate our query language (QL/PQL) through bigQuery
4 Responding to page events (ViewAction)
5 Register to respond to RL

import {Store,ViewAction} from 'plume2'
import LoadingActor from 'loading-actor'
import UserActor from 'user-actor'
import TodoActor from 'todo-actor'


// 响应页面事件的逻辑处理
class AppViewAction extends ViewAction {
    //show simple dispatch 显示简单分派
    // when dispatch finished if status had changed,  如果状态已更改,则在调度完成时,
    //each Relax component received message 每个组件都接收到消息
    update = () => {
        //将计算的任务分派的到actor
        //然后根据actor的返回值 重新聚合新的store的state
        //该为单分派  当dispatch结束 store的state发生改变的时候
        //UI容器组件(StoreProvider,Relax) 会收到通知重新re-render UI
        this.store.dispatch('update')
    }
//  show multiple dispatch in a transaction  在事务中显示多个分派
    save = () =>{
        //事务分派
        //很多场景下  计算应该是原子类型的  我们想一想 dispatch 结束才通知UI去re-render
        //这个时候我们就可以开启事务控制
        // transaction  会返回值来判断在dispatch  过程中有没有发生错误
        // 如果发生错误  数据会自动回滚到上一次的状态  避免脏数据
        // 我们也可以指定  自定义的回滚处理
        // this.transaction(()=>{/*正常逻辑*/},()=>{/*自定义的回滚函数*/})
        this.store.transaction(()=>{
            this.store.dispatch('loading:end')
            //这个地方可以得到上一次的dispatch之后的结果
            //如:
            const loading = this.state().get('loading')
            this.store.dispatch('init:user',{id:1,name:'plume2'})
            this.store.dispatch('save')
        })
    }
}


class AppStore extends Store{
//   聚合Actor
//   通过reduce各个actor的defaultState
// 聚合出store的state作为source data
bindActor(){
    //plume2  直接传递Actor的class
    return [LoadingActor,UserActor,TodoActor]
}
bindViewAction(){
    return{
        AppViewAction
    }
}
}

Store public-API

// 绑定需要聚合的Actor
bindActor(): Array<Actor | typeof Actor>

bindViewAction(): IViewActionMapper

// 事务控制dispatch
// dispatch: 正常逻辑
// rollBack: 自定义回滚逻辑  默认是自动回滚到上一次的状态
//返回是否发生回滚

transaction(dispatch:Dispatch,rollBack:RollBack):boolean;

// 计算QL

bigQuery(ql:QueryLang):any

// 当前store聚合的状态
state():IMap;

// 定义store状态更新通知
subscribe(cb:Handler):void;

// 取消订阅
unsubscribe(cb:Handler):void;

StoreProvider

StoreProvider container component connects our React component and AppStore. Provide data sources to React components

The main tasks in StoreProvider are:
1 Initialize our AppStore
2 Bind AppStore objects to the context of React components
3 Relay is the store object obtained through the context
4 Monitor store state changes

Friendly reminder: We also provide debug mode

Turn on the debug mode and we can track the data across the entire link

Track the processing of the dispatch actor of the store, the calculation of QL by relax, etc.

import React, { Component } from 'react'
import {StoreProvider} from 'iflux2'
import AppStore from './store'

//enable debug
@StoreProvider(AppStore,{debug:true})
class ShoppingCart extends Component{
    render(){
        return(
            <Scene>
                <HeaderContainer/>
                <ShoppingCart/>
                <BottomToolBarContainer/>
            </Scene>
        )
    }
}

Relax

Relax is a very important container component in plume2, similar to dependency injection of Spring container
The core function will be based on the data declared in the relaxProps of the subcomponent,
The value of the property is calculated intelligently and then transparently transmitted to the sub-component as this.props.relaxProps

In order to solve the problem of verbose transmission of React's props layer by layer

Calculation rules
1 The value of the state of the store, which directly gives an immutable path, such as count:'count',todoText:['todo',1,'text']
2 The method of the store has the same name as the method. For example: destroy:noop, we prefer to use ActionCreator to handle the side effect of the UI separately
3 If the attribute is'viewAction', directly inject the ViewAction bound in the store
4 If the attribute is the result of QL injection QL calculation, if PQL will automatically bind the context of the store

@Relax
export default class Footer extends React.Component{
    static relaxProps = {
        changeFilter:noop,
        clearCompleted:noop,
        count:countQL,
        loadingPQL:loadingPQL,
        filterStatus:'filterStataus',
        viewAction:'viewAction'
    }
    render(){
        const{
            changeFilter,
            clearCompleted,
            count,
            filterStataus,
            viewAction
        } = this.props.relaxProps
    }
    //...
}

QL/PQL

Why do we need a QL
1 We regard store state as source data, because the data displayed by the UI may need to be combined according to our source data
2 We need the UI data to be reactive. When the source data changes, @Relax will recalculate our QL
3 Imperative programming. Manually and accurately handle the dependence and update of the data. Reactive will automatically handle the dependence of the data. However, the same QL may be executed multiple times, resulting in computational waste.
However, there is no need to worry about QL supporting cache to ensure that QL will not double-calculate when the data corresponding to path has not changed.

QL = Query Lang
The source of the custom query syntax data is the data returned by the state of the store

Syntax QL(displayName,[string|array|QL...RelaxContainer,fn])

displayName, mainly to help us better log tracking in the debug state

string array QL: string array is the path of immutable get, QL other QL (support wireless nesting)

fn: The callback function bigQuery that can calculate the state will get all the values corresponding to the path in all the arrays and pass them to fn as a parameter


// 返回:{
//     id:1,
//     name:'iflux2',
//     address:{
//         city:'南京'
//     }
// }

store.state()

//QL计算的结果值是'iflux2南京'
const helloQL = QL('helloQL',[
    'name',
    ['address','city'],
    (name,city)=>`${name}${city}`
])

Store.bigQuery(helloQL)

QL in QL

import {QL} from 'plume2'
const loadingQL = QL('loading',['loading',loading=>loading])
const userQL = QL('userQL',[
    //query lang 支持嵌套
    loadingQL,
    ['user','id'],
    (loading,id)=>({id,loading})
])

We have made some major changes in version 0.3.2

plume2 is a new starting point for us. plume2 is the starting point for us to typescript. plume2 is completely thinking about the characteristics and implementation of the framework from the static and compilation perspectives of typescript. We hope that plume2 is light and simple and consistent, while giving elegant code inspection error prompts.
The log tracking of the whole link wants our development to be easier
In our practice, there will be some places that are not detailed enough. We need to constantly improve. How to think about improvement is not overriding. The development experience is as important as the user experience.

inprovements

1 Kill DQL, DQL is a bit tasteless. This is the reality. There is an ideal difference. The replacement of template variables that require dynamic recursion in the implementation process is also more guilty.
More importantly, the source of DQL's dynamic data can only be the props of React's Component, which brings some inflexibility and limitation. We design DQL.
Or what QL's original intention is to obtain data declarative (Declarative) and the data itself reactive (reactive) In order to solve this problem, we designed a simpler
PQL(partial Query Lang)

import {PQL,Relax} from 'plume2'
const helloPQL = PQL(index =>QL([
    ['user',index,'name'],
    (name)=>name
]))

@Relax 
class HelloContainer extends React.Component{
    static relaxProps = {  
        hello:helloPQL   //自动绑定store的上下文
    }

    render(){
        const value = hello(1)
        return <div>{value}</div>
    }
}

Simple and clear realization of more flexible parameter entry does not currently support QPL nested QPL.

1 More comfortable development experience
Sometimes, in order to quickly test some of our store methods in the browser console such as (chrome console), we will write

  class AppStore extends Store {
    constructor(props: IOptions){
        if(_DEV_){
            window['store'] = this
        }
    }
}

In this way, you can directly call _store in the console to quickly test, but it is a little annoying to write each page like this often.
Write a construction method for no reason. It's also quite boring.
In some SPA or react-native multi-page _store will be repeatedly covered.
This problem can be solved from the framework level. The framework is automatically bound when the debug specific of the application is turned on. To simplify this process

开启debug-mode

@StoreProvder(AppStore,{debug:true})
class HelloApp extends React.Component{
    render(){
        return <div/>
    }
}

plume2 will automatically bind _plume2App to the window, and each key is the component name under storeprovider
image.png
This way the little friends can play as much as they want.

1 Better event handling module Currently, the handlers of our UI interaction events are all in the store, because we hope that the UI is less-logic so that it can be universal to our business layer.
Previously, the method of injecting our store through relax and relaxProps gave the UI interaction logic such as:


const noop = ()=>{}
@Relax 
class HelloApp extends React.Component{
    props:{
        relaxProps?:{
            OnInt:Function;
            onReady:Function;
            onShow:Function
        }
    }
    static relaxProps = {
        onInit:Function,
        onReady:Function,
        onShow:Function
    }
}

This feature is simply injected by relax and it's done. There is a rule as long as the name of the method is the same as the method name of the store. But in actual operation, I found that writing the injection and then writing the typeScript type definition is really too heavy and exhausting.
What's more, we may have a leaf node component that just wants to call back an event and must be injected through relax. If there is an improper design of the scene of the list data,
If each item is a relax page with 200 pieces of data, it is a 200relax component.
The essence of relax is to subscribe container Component,
It will monitor the changes of the store in real time and the performance of the 200 crashing performance drops.
SO We need to think about how to define UI UI = s(state) in react. In fact, this is not complete. This only defines the display part of the UI and interactive interaction. Functional view events are side effects, so a more complete definition should be UI. = f(state,action),
Continue thinking, is it state? From the perspective of flux, look at state = store(initState, action). Are you familiar with all actions? Is there any connection? In fact, one is the exit and the other is the entrance

image.png
So with this in mind, we can design an ActionCreator module.

Come, come, code.

const actionCreator = ActionCreator()
actionCreator.creat('INIT',(store,num:string)=>{
    //biz logic
    store.dispatch('init',num)
})

class AppStore extends Store{
    //将store绑定到AcitonCreator
    bindActionCreator(){
        return actionCreator;
    }

    // 除了在用actionCreator.create创建
    //event  handler 也可以直接在store中
    @Action("INIT")
    init(num:string){
        this.dispatch('init',num)
    }
}

const HelloApp = () =>{
    <div>
        <a onClick={actionCreator.fire('INIT',1)}>吼吼吼</a>
    </div>
}

One problem with this approach is that ActionCreator is a singleton. This will cause the event to be overwritten by the context of the store when a page is repeatedly rendered multiple times.
Based on this consideration, it is still through context injection binding, so ViewAction was designed in 1.0.0 to solve this problem

ViewAction

import {ViewAction,Store} from 'plume2'

class LoadingViewAction extends ViewAction{
    loading = () =>{
        this.store.dispatch('loading:start')
    }
}

class FilterViewAction extends ViewAction{
    filter = (text:string) =>{
        this.store.dispatch('filter:text',text)
    }
}


//bind to store
class AppStore extends Store{
    bindViewAction(){
        return{
            LoadingViewAction,
            FilterViewAction
        }
    }
}

//how to injected to ui

class Filter extends React.Component{
    props:{
        relaxProps?:{
            //代码自动提示  参考example中的例子
            viewAction:TViewAction<typeof {LoadingViewAction,FilterViewAction}>
        }
    }
    static relaxProps = {
        viewAction:'viewAction'
    }

    render(){
        const {viewAction} = this.props.relaxProps
    }
}
What age are you still using strings naked, you are the devil string. .

Yes, we add the enumeration type that we add the string to once to solve the dispatch to the actor and other constant strings

export default ActionType('INCREMENT','DECREMENT')

image.png
More complex structure,

const Actions = actionType({
    PDF:'application/pdf',
    Text:'text/plain',
    JPEG:'image/jpeg'
})

So just use it directly, it is recommended to use constant string enumeration, why?

export const enum Command{
    LOADING="loading",
    FILTER_TEXT="filter:text"
}

No gold is pure, no one is perfect, in reality image.png
image.png
image.png


HappyCodingTop
526 声望847 粉丝

Talk is cheap, show the code!!