zhouyk.github.io

docs

Follow me on GitHub

femo docs

Background

I have been always using redux on my work since 3 years ago.I have learned a lot from redux.Until today, redux also has influences on me and inspires me to create femo.

femo simplifies redux and focus on the data, exclude the process. femo is totally function-centric. There is a strong abstract for data, called model.All operations are around model.

Model

Model is just plain object. It consists of different nodes those present structure and maintain data. Model is a blueprint what data structure you want.

model

We can choose which nodes to be maintainable node. Maintainable node means it can be mutated.We declare maintainable node like below:

const person = gluer((data, state) => {
  return { 
    ...state,
    ...data
   }
}, { name: 'person', age: 10 });

Above illustrates:

  1. person has an initial value { name: ‘person’, age: 10 } which indicates person’s structure and type.
  2. person has a pure function to produce a new state by the input data and the current state

Now we can use person to define models:

const model = {
  	tom: person,
  	jack: person,
  	rose: person,
  };

Defined a model simply. person here, is more like a template(a bit like the concept of Class).It can be reused.

Finally, we let model work:

const store = femo(model);

// subscribe
const unsubscribe = store.subscribe([store.model.tom], (tom) => {
  // if tom changes, this callback will be called
});

console.log(store.getState());
// { tome: { name: 'person', age: 10 }, jack: { name: 'person', age: 10 }, rose: { name: 'person', age: 10 } }

console.log(store.referToState(store.model));
// same as above.

console.log(store.getState() === store.referToState(store.model));
// true

console.log(store.referToState(store.model.tom));
// { name: 'person', age: 10 }

// unsubscribe
unsubscribe();

The complete code:

import femo, { gluer } from 'femo';

const person = gluer((data, state) => {
  return { 
    ...state,
    ...data
   }
}, { name: 'person', age: 10 });

const model = {
  	tom: person,
  	jack: person,
  	rose: person,
  };

const store = femo(model);

// subscribe
const unsubscribe = store.subscribe([store.model.tom], (tom) => {
  // if tom changes, this callback will be called
});

console.log(store.getState());
// { tome: { name: 'person', age: 10 }, jack: { name: 'person', age: 10 }, rose: { name: 'person', age: 10 } }

console.log(store.referToState(store.model));
// same as above.

console.log(store.getState() === store.referToState(store.model));
// true

console.log(store.referToState(store.model.tom));
// { name: 'person', age: 10 }

// unsubscribe
unsubscribe();

API

gluer

declare maintainable node

Basic

param initial value illustration
fn (data, state) => data pure function for mutation
initialState - the init value of maintainable node indicates the data type and structure

Suggestion

There is a strong suggestion initialState should not be empty.Further more, the fn’s return should keep the same pattern with the initialState.We need keep maintainable node’s appearance and fact same.

Fox example:

const friend = gluer((data, state) => {
  const result = { ...state, ...data };
  // result should be like this: { hobby: string, company: string, name: string }
  return result;
}, {
     hobby: 'beer',
     company: 'facebook',
     name: 'John'
   });

In typescript, the rule above is confirmed.

Advanced

maintainable node may contain complicated calculation or deep nested data structure.At this moment, we intend to split them into parts. gluer serves the ability that takes complicated calculation or deep nested data structure into different small parts.

Fox example:


const goods = gluer((data, state) => {
  return {
    ...state,
    ...data,
    origin: {
      ...state.origin,
      ...data.origin,
      company: {
        ...state.origin.company,
        ...data.origin.company
      }
    }
  }
}, { 
  price: 100, 
  origin: {
    city: 'Mumbai',
    country: 'India',
    company: {
      legalPerson: 'Vishal',
      address: 'Ganges'
    }
  },
  count: 1000
})

Nested data structure makes assignment indirect.

We can split it into flat parts!

const price = gluer(100);
const count = gluer(1000);
const company = gluer((data, state) => {
  return {
    ...state,
    ...data
  }
}, { 
  legalPerson: 'Vishal',
  address: 'Ganges'
 });

const origin = gluer((data, state) => {
  // state is latest
  // like { city: string, country: string, company: { legalPerson: string, address: string } }
  
  // here just maintains two fields: city, country, the company field is handled by company node.
  // so origin node here should not handle the origin field, although the state is complete.
  return {
    ...state,
    city: data.city,
    country: data.country
  }
}, {
  city: 'Mumbai',
  country: 'India',
  company
});
const goods = gluer((data, state) => {
  // state is latest
  return state;
}, {
  price,
  origin,
  count
});

We let goods work.

const model = {
  goods
};
const store = femo(model);

console.log(store.referToState(store.model.goods));

How can we index price or company? Just does above!

console.log(store.referToState(store.model.goods.price)); // 100
console.log(store.referToState(store.model.goods.origin.company)); // { legalPerson: 'Vishal',  address: 'Ganges' }

Tips

When input store.model.goods.price or store.model.goods.origin.company, there may be no code prompt in javascript. In typescript there are.

store

Store has properties below.

name type illustration
getState Function Return current state
model plain object The model input femo
referToState Function Return the data indexed by model. If the input value is not in the model, return undefined.
hasModel Function Return boolean. Determine whether the input value is in the model.
subscribe Function Return unsubscribe function. const unsubscribe = subscribe([deps], callback);