11.0.1-beta.1 • Published 17 days ago

resy v11.0.1-beta.1

Weekly downloads
-
License
MIT
Repository
github
Last release
17 days ago

GitHub license GitHub Workflow Status Codecov npm type definitions npm bundle size react

Features

  • 😎 Easy!!!
  • 😎 Support for class and hook
  • 😎 Better performance optimization

Install

npm i resy

# yarn add resy
# pnpm add resy

Usage

import { createStore, useStore, ComponentWithStore } from "resy";

const store = createStore({ count: 0 });

// for hook component
function App() {
  const { count } = useStore(store);  // or store.useStore();
  return (
    <>
      {count}
      <button onClick={() => store.count++}>increase</button>
    </>
  );
}

// for class component
class AppClass extends ComponentWithStore {
  
  store = this.connectStore(store);
  
  render() {
    const { count } = this.store;
    return (
      <>
        {count}
        <button onClick={() => { store.count++; }}>increase</button>
      </>
    );
  }
}

Edit on CodeSandbox

Basic API

resy requires the version of React v >= 16.8

APIDescription
createStoreCreate a store container for state
useStoreUse state from the store container generated by createStore
setStateUpdate data
syncUpdateSynchronously update data

Detailed introduction of api

the store returned by createStore can be shared globally
const demoStore1 = createStore({
  count: 0,
  text: "hello",
});
paradigm type
type DemoStateType = { count: number; text?: number | string };
// In this way, the type of text can be
// more accurately identified as number or string or undefined
const demoStore2 = createStore<DemoStateType>({
  count: 0,
});
function return
// This is a very important feature for retrieving the latest time or other data.
const demoStore3 = createStore(() => {
  return {
    count: 0,
    time: Date.now(),
  };
});
initial function attribute
const demoStore4 = createStore({
  count: 0,
  increase() {
    // this point store object, as follows example
    // The updates and usage of these APIs will be detailed in subsequent chapters
    this.count++;
    // this.setState({ count: this.count + 1 });
    // this.restore();
    
    // demoStore4.count++;
    // demoStore4.setState({ count: demoStore3.count + 1 });
  },
});
general use
import { createStore } from "resy";

type StateType = {
  count: number;
  text: string;
  info: { name: string };
  ageList: { age: number }[];
  increase(): void;
  inputValue?: string;
};

// The generated store can be shared globally
const store = createStore<StateType>({
  count: 0,
  text: "hello",
  info: { name: "Jack" },
  ageList: [{age: 12}, { age: 16 }],
  increase() {
    this.count++;
  },
});
createStore options item - unmountRestore
// Store such as login and theme can set unmountRestore to false
// so that it will not be reset globally.
const userStore = createStore<{ userName: string; userId: number }>(
  {
    userName: "wenmu",
    userId: 0,
  },
  {
    unmountRestore: false,
  },
);
const themeStore = createStore<{ themeStyle: "dark" | "light" }>(
  {
    themeStyle: "dark",
  },
  {
    unmountRestore: false,
  },
);
deconstruction usage mode
import { useStore } from "resy";

function App() {
  const { count, text } = useStore(store);
  // or
  // const { count, text } = store.useStore();
  
  return (
    <>
      <p>{count}</p>
      <p>{text}</p>
    </>
  );
}
Mixed use of store
import { useStore } from "resy";

function App() {
  const { userName } = userStore.useStore();
  const { themeStyle } = themeStore.useStore();
  
  return (
    <>
      <p>{userName}</p>
      <p>{themeStyle}</p>
      <button onClick={() => { userStore.userName = "LF" }}>nameChange</button>
      <button onClick={() => { themeStore.setState({ themeStyle: "light" }) }}>themeChange</button>
    </>
  );
}
direct read usage mode
import { useStore } from "resy";

function App() {
  const state = store.useStore();
  
  return (
    <>
      <p>{state.count}</p>
      <p>{state.text}</p>
    </>
  );
}
The method of deconstructing StoreUtils
import { useStore } from "resy";

function App() {
  const {
    count, text,
    // The use of these api will be described in detail later.
    setState, syncUpdate, restore, subscribe,
  } = store.useStore();
  
  return (
    <>
      <p>{count}</p>
      <p>{text}</p>
    </>
  );
}
direct assignment update
import { useStore } from "resy";

function App() {
  const { count, text } = store.useStore();
  
  // Updates can be assigned directly
  function btn2() {
    store.count++;
    store.text = "456asd";
  }
  
  return (
    <>
      <p>{count}</p>
      <p>{text}</p>
    </>
  );
}
ComponentWithStore、PureComponentWithStore
import { ComponentWithStore, PureComponentWithStore } from "resy";

/**
 * @description ComponentWithStore is inherited from React Component,
 * PureComponentWithStore is inherited from React PureComponent;
 */
class AppClass extends ComponentWithStore {

  store = this.connectStore(store);

  render() {
    const { count } = this.store;
    return (
      <>
        {count}
        <button onClick={() => { store.count++; }}>button +</button>
      </>
    );
  }
}

class PureAppClass extends PureComponentWithStore {
  store = this.connectStore(store);

  render() {
    const { count } = this.store;
    return (
      <>
        {count}
        <button onClick={() => { store.count++; }}>button +</button>
      </>
    );
  }
}
Mixed use of store
import { ComponentWithStore, createStore } from "resy";

/**
 * @description The update methods of internal "this.userStore" and "this.themeStore"
 * are the same as those of the connected store itself, and can be called directly.
 */
class AppClass extends ComponentWithStore {

  userStore = this.connectStore(userStore);

  themeStore = this.connectStore(themeStore);

  render() {
    const { userName } = this.userStore;
    const { theme } = this.themeStore;
    return (
        <>
          <span>{userName}</span>
          <span>{theme}</span>
          <button onClick={() => { this.userStore.userName = "LD" }}>
            nameChange
          </button>
          <button onClick={() => { this.themeStore.setState({ theme: "light" }) }}>
            themeChange
          </button>
        </>
    );
  }
}
import { useStore } from "resy";

function App() {
  const {
    info: { name }, ageList, inputValue,
  } = store.useStore();
  
  function btn2() {
    // store.info.name = "Jack";   // Invalid update
    // store.ageList[0] = { age: 7 };   // Invalid update
    
    store.info = { name: "Jack" }; // Effective update
    store.ageList = [{age: 7}];   // Effective update
  }
  
  return (
    <>
      <p>{name}</p>
      {ageList.map(item => `Age:${item}`)}<br/>
      <button onClick={btn2}>btn2</button>
    </>
  );
}

hook

import { createStore } from "resy";

let textProExecCounter = 0;
let textProParamChangeExecCounter = 0;

const store = createStore({
  count: 0,
  countChange() {
    this.count++;
  },
  text: "ok",
  getTextPro(str?: number | string) {
    textProExecCounter++;
    /**
     * In version 11, there are plans to introduce a concept aligned with JavaScript's logical understanding,
     * tentatively referred to as 'pseudo-auto-computation', which can be initially understood as 'computed properties'.
     * Here, if getTextPro functions as a computed property
     * (meaning the function getTextPro is treated as such when being destructured via useStore),
     * then 'this' is akin to the renderStore returned by useStore(store),
     * and it needs to comply with the rules of hooks usage (i.e., used at the top level).
     */
    const { text } = this;
    // Returns the final computed result for rendering.
    return `${str ?? ""}_${text}_world`;
  },
  getTextProParamChange(param?: number | string) {
    textProParamChangeExecCounter++;
    const { text } = this;
    // Returns the final computed result for rendering.
    return `${param ?? ""}_${text}_world`;
  },
});

const App = () => {
  const { count, getTextPro, getTextProParamChange } = store.useStore();

  return (
    <div>
      <div>count:{count}</div>
      {/**
       * However, unlike Vue's computed properties,
       * Resy adopts a feature that closely aligns with the native understanding of JavaScript.
       * Computed properties are, after all, functions; thus, in this case, the function invocation approach is utilized.
       */}
      <div>textPro:{getTextPro("none")}</div>
      <div>textProParamChange:{getTextProParamChange(count)}</div>
      <button
        onClick={() => {
          /**
           * countChange is not treated as a computed property and does not need to be destructured from useStore.
           * Calling it directly through the store, it is then used as an action.
           */
          store.countChange();
        }}
      >
        countChange
      </button>
      <button
        onClick={() => {
          // When updating text here, getTextPro acts as a computed property and will update the rendering of the App component.
          store.text = "hello";
        }}
      >
        textChange
      </button>
    </div>
  );
};

class

import { createStore, ComponentWithStore } from "resy";

let textProExecCounter = 0;
let textProParamChangeExecCounter = 0;

class App extends ComponentWithStore {
  store = this.connectStore(store);

  render() {
    const { count, getTextPro, getTextProParamChange } = this.store;
    return (
      <div>
        <div>count:{count}</div>
        <div>textPro:{getTextPro("none")}</div>
        <div>textProParamChange:{getTextProParamChange(count)}</div>
        <button onClick={() => store.countChange()}>
          countChange
        </button>
        <button onClick={() => store.text = "hello"}>
          textChange
        </button>
      </div>
    );
  }
}
import { useStore } from "resy";

function App() {
  const { count, text } = store.useStore();
  
  return (
    <>
      <div>{count}</div>
      <div>{text}</div>
      <button
        onClick={() => {
          store.setState({
            text: "demo-setState",
            count: count + 1,
          });
        }}
      >
        btn
      </button>
    </>
  );
}
setState's callback
import { useStore } from "resy";

function App() {
  const { text } = store.useStore();
  
  return (
    <button
      onClick={() => {
        store.setState({
          text: "cur-text",
        }, nextState => {
          console.log(nextState.text === "cur-text"); // true
        });
      }}
    >
      {text}
    </button>
  );
}
parameters of callback for setState

the difference between the callback of setState and the callback of this.setState of class components

  • reading this.state in the callback function of this.setState in the class component obtains the latest data in the current round of updates.
import { Component } from "react";

class TestClassX extends Component {
  constructor() {
    super();
    this.state = { count: 0, text: "class-x" };
  }
  
  render() {
    const { count, text } = this.state;
    return (
      <>
        {count},{text}
        <button
          onClick={() => {
            this.setState({
              text: "Try",
            }, () => {
              console.log(this.state.count === 9);  // true
            });
            this.setState({ count: 9 });
          }}
        >
          btn
        </button>
      </>
    );
  }
}
  • however, the nextState of the callback function of resy's setState is the latest data in the current synchronization phase, but it does not belong to the latest data after the final round of updates.
import { useStore, createStore } from "resy";

const store = createStore({count: 0, text: "hello"});

function App() {
  const { text } = store.useStore();
  
  return (
    <button
      onClick={() => {
        store.setState({
          text: "cur-text",
        }, nextState => {
          console.log(nextState.text === "cur-text"); // true
          console.log(nextState.count === 0); // true
          console.log(store.count === 9); // true
        });
        store.setState({count: 9});
      }}
    >
      {text}
    </button>
  );
}
parameters of the function type of setState
import { useStore } from "resy";

const store = createStore({count: 0, text: "hello"});

function App() {
  const { count, text } = store.useStore();
  
  function btnClick1() {
    store.setState(() => {
      // Returns the object that will eventually be updated
      // through the calculation of complex business logic
      return {
        count: count + 1,
        text: "B-Way-setState-with-function",
      };
    });
  }
  
  function btnClick2() {
    store.count = 9;
    // The prevState parameter of the function
    store.setState(prevState => {
      console.log(prevState.count === 9);  // true
      console.log(store.count === 9);  // true
      return {
        text: "ok",
      };
    });
  }
  
  return (
    <>
      <div>{count}</div>
      <div>{text}</div>
      <button onClick={btnClick1}>btn-1</button>
      <button onClick={btnClick2}>btn-2</button>
    </>
  );
}
import { useStore, syncUpdate } from "resy";

/**
 * @description 🌟 The main purpose of syncUpdate is to solve the problem
 * that input box updates such as input cannot be updated in an asynchronous environment.
 */
function App() {
  const { inputValue } = store.useStore();
  
  function inputChange(event: React.ChangeEvent<HTMLInputElement>) {
    store.syncUpdate({
      inputValue: event.target.value,
    });
    // @example B
    // store.syncUpdate(prevState => {
    //   // prevState is same as setState's prevState.
    //   return {
    //     inputValue: event.target.value,
    //   };
    // });
    // @example C
    // You can also use the callback function
    // store.syncUpdate({
    //   inputValue: event.target.value,
    // }, nextState => {
    //   console.log(nextState);
    // });
  }
  
  return (
    <input value={inputValue} onChange={inputChange}/>
  );
}
hook
import { useStore } from "resy";

// Updates to count data will not cause Text components to re-render
function Text() {
  const { text } = store.useStore();
  return <p>{text}</p>;
}

// Updates to text data will not cause Count components to re-render
function Count() {
  const { count } = store.useStore();
  return <p>{count}</p>;
}

function App() {
  const { increase, name } = store.useStore();
  
  return (
    <>
      <Text/>
      <Count/>
      <div>{name}</div>
      <button onClick={() => { store.name = "app"; }}>btn-name</button>
      <button onClick={increase}>btn+</button>
      <button onClick={() => { store.count-- }}>btn-</button>
    </>
  );
}
class
import { useStore, ComponentWithStore } from "resy";

// Updates to count data will not cause Text components to re-render
class TextClass extends ComponentWithStore {

  store = this.connectStore(store);
  
  render() {
    const { text } = this.store;
    return (
      <p>{text}</p>
    );
  }
}

// Updates to text data will not cause Count components to re-render
class CountClass extends ComponentWithStore {

  store = this.connectStore(store);

  render() {
    const { count } = this.store;
    return (
      <p>{count}</p>
    );
  }
}

class AppClass extends ComponentWithStore {

  store = this.connectStore(store);

  render() {
    const { increase, name } = this.store;
    return (
      <>
        <Text/>
        <Count/>
        <div>{name}</div>
        <button onClick={() => { store.name = "app" }}>btn-name</button>
        <button onClick={increase}>btn+</button>
        <button onClick={() => { store.count-- }}>btn-</button>
      </>
    );
  }
}

Advanced API

APIDescription
useConciseStateConcise version of useState
subscribeSubscribe for changes in store data generated by createStore
useSubscriptionHook of subscribe
restoreRestore data of store, with re-render effect
setOptionsSet the options parameter of createStore

Detailed introduction of api

import { useConciseState } from "resy";

const initialState = {
  count: 123,
  text: "hello-consice",
};

function App() {
  const { count, text, store, setState } = useConciseState(initialState);
  
  return (
    <>
      <div
        onClick={() => {
          setState({
             count: count + 1,
             text: "ASD",
          });
          // or
          // store.count++;
          // store.text = "ASD";
          // or
          // store.setState({
          //   count: count + 1,
          //   text: "ASD",
          // });
          // store has all the data of useConciseState
          // and the restore, syncUpdate, and subscribe methods
        }}
      >
        {count}
      </div>
      <div>{text}</div>
    </>
  );
}

restore、syncUpdate、subscribe these api can also be deconstructed and used directly.

import { useEffect } from "react";
import { useConciseState } from "resy";

function App() {
  const { count, text, restore, syncUpdate, subscribe } = useConciseState(initialState);
  
  useEffect(() => {
    return subscribe(({ effectState }) => {
      console.log(effectState);
    }, ["text"]);
  }, []);
  
  return (
    <>
      <input
        value={text}
        onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
          syncUpdate({text: event.target.value});
        }}
      />
      <div onClick={() => restore()}>reset-btn</div>
      <div>{text}</div>
    </>
  );
}

Advantages of useConciseState

import { useConciseState, ConciseStoreHeart } from "resy";

type State = {
  count: number;
  text: string;
};

function ChildOne(props: ConciseStoreHeart<State>) {
  const { store } = props;
  const { count, text } = useStore(store);

  return (
    <>
      <p>ChildOne-count:{count}</p>
      <p>ChildOne-text:{text}</p>
      <button
        onClick={() => {
          store.setState({
            count: 999,
            text: "ChildOneSetStateNewText",
          });
        }}
      >
        childOneBtn
      </button>
    </>
  );
}

function ChildTwo(props: ConciseStoreHeart<State>) {
  const { store } = props;
  const [data, setData] = useState({ count: 0, text: "hello" });

  store.useSubscription(({ nextState }) => {
    setData(nextState);
  });

  return (
    <>
      <p>ChildTwo-count:{data.count}</p>
      <p>ChildTwo-text:{data.text}</p>
    </>
  );
}

const App = () => {
  const { count, text, store } = useConciseState<State>({
    count: 0,
    text: "hello",
  });

  return (
    <>
      <p>{count}</p>
      <p>{text}</p>
      <ChildOne store={store} />
      <ChildTwo store={store} />
      <button onClick={() => {
        store.setState({
          count: 1,
          text: "world",
        });
      }}>change</button>
    </>
  );
};

global subscribe

// You can also subscribe to a non-lifecycle data monitor directly.
const unsub = store.subscribe(() => {
  // ... to do anything
}, ["count", "text"]);

// cancel subscirbe
// unsub();

empty keys

store.subscribe(() => {
  // ... to do anything
}, []);
// [] or no state keys is equal
// no state keys
store.subscribe(() => {
  // ... to do anything
});

general use

import { useEffect } from "react";
import { useStore } from "resy";

function App() {
  const { count } = store.useStore();
  
  // Here is an example of a function component.
  // If it is a class component, it can be used in componentDidMount.
  useEffect(() => {
    /**
     * @param listener: subscription monitoring callback function
     * @param stateKeys: subscription listens for changes in certain data fields of a specific store.
     * If empty, default listens for changes in any one of the data in store.
     * @return Unsubscribe: unsubscribe to the function of listening
     */
    const unsubscribe = store.subscribe(({
      effectState, prevState, nextState,
    }) => {
      /**
       * effectState:Currently changing data
       *   nextState:Data after change
       *   prevState:Data before change
       */
      console.log(effectState, prevState, nextState);
    }, ["count", "text"]);
    
    // unsubscribe();
    return () => {
      unsubscribe();
      // ... to do else anything
    };
  }, []);
  
  function btnClickA() {
    store.count++;
  }
	
  function btnClickB() {
    store.text = "control btn-b click update text state value";
  }
	
  function btnClickC() {
    store.setState({
      count: count + 1,
      text: "control btn-c click update text state value",
    });
  }
  
  return (
    <>
      <p>{count}</p>
      <button onClick={btnClickA}>btn-A</button><br/>
      <button onClick={btnClickB}>btn-B</button><br/>
      <button onClick={btnClickC}>btn-C</button>
    </>
  );
}
import { useEffect } from "react";
import { useStore, useSubscription } from "resy";

function App() {
  const { count } = store.useStore();

  useSubscription(store, ({
    effectState, prevState, nextState,
  }) => {
    console.log(effectState, prevState, nextState);
  }, ["count"]);
  
  function btnClick() {
    store.count++;
  }
  
  return (
    <>
      <p>{count}</p>
      <button onClick={btnClick}>btn</button><br/>
    </>
  );
}
import { useEffect } from "react";
import { useStore } from "resy";

function App() {
  const { count } = store.useStore();

  store.useSubscription(({
    effectState, prevState, nextState,
  }) => {
    console.log(effectState, prevState, nextState);
  }, ["count"]);
  
  function btnClick() {
    store.count++;
  }
  
  return (
    <>
      <p>{count}</p>
      <button onClick={btnClick}>btn</button><br/>
    </>
  );
}
import { useStore } from "resy";

function App() {
  const { count, text } = store.useStore();
  
  return (
    <>
      <div>{count}-{text}</div>
      <div
        onClick={() => {
          // data recover initial
          store.restore();
          // You can also add callback functions in the restore function
          // store.restore(nextState => {
          //   console.log(nextState);
          // });
        }}
      >
        reset-btn
      </div>
    </>
  );
}
import { createStore, useStore } from "resy";

const timeStore = createStore(() => {
  return {
    now: Date.now(),
  };
});

function App() {
  const { now } = useStore(timeStore);
  
  return (
    <>
      <div>now:{now}</div>
      <div
        onClick={() => {
          // time data now recover and also changed initial,
          // because of initialState is function return.
          store.restore();
        }}
      >
        reset-btn
      </div>
    </>
  );
}
function App() {
  return (
    <button
      onClick={() => {
        // Use less scenes, use it with caution
        // You can change the unmountRestore parameter setting of createStore
        store.setOptions({ unmountRestore: false });
      }}
    >
      btn
    </button>
  );
}

License

MIT License (c) 刘善保

11.0.1-beta.1

17 days ago

11.0.1-beta.0

24 days ago

11.0.0

2 months ago

11.0.0-rc.2

2 months ago

11.0.0-rc.0

2 months ago

11.0.0-rc.1

2 months ago

11.0.0-beta.0

2 months ago

11.0.0-alpha.1

2 months ago

11.0.0-alpha.2

2 months ago

11.0.0-alpha.3

2 months ago

11.0.0-alpha.4

2 months ago

11.0.0-alpha.0

2 months ago

10.0.0

6 months ago

10.0.1

6 months ago

10.0.2

6 months ago

10.0.2-beta.3

6 months ago

10.0.2-beta.2

6 months ago

10.0.2-beta.1

6 months ago

8.1.0

9 months ago

9.0.0-beta.1

9 months ago

9.0.0

9 months ago

7.1.1

1 year ago

7.1.0

1 year ago

8.0.0-beta.2

1 year ago

8.0.0-beta.1

1 year ago

8.0.0-beta.4

1 year ago

8.0.0-beta.3

1 year ago

5.2.1

1 year ago

6.0.0

1 year ago

8.0.0-rc.1

1 year ago

8.0.0-rc.2

1 year ago

8.0.0-rc.3

1 year ago

7.0.0

1 year ago

8.0.0-rc.4

1 year ago

8.0.0

1 year ago

5.0.0-alpha.6

1 year ago

5.1.3

1 year ago

5.0.0-alpha.5

1 year ago

5.1.2

1 year ago

5.0.0-alpha.4

1 year ago

5.1.1

1 year ago

5.1.0

1 year ago

5.0.0-beta.2

1 year ago

5.0.0-beta.3

1 year ago

5.0.0-beta.1

1 year ago

4.0.0-alpha.1

2 years ago

4.0.0-alpha.2

2 years ago

3.0.3

2 years ago

3.0.2

2 years ago

3.0.1

2 years ago

3.0.0

2 years ago

4.0.5

1 year ago

4.0.4

1 year ago

4.0.1

2 years ago

4.0.0

2 years ago

4.0.3

2 years ago

4.0.2

2 years ago

5.2.0

1 year ago

5.0.1

1 year ago

5.0.0

1 year ago

2.0.3

2 years ago

2.0.5

2 years ago

2.0.4

2 years ago

1.2.0

2 years ago

1.8.2

2 years ago

1.8.1

2 years ago

1.8.0

2 years ago

1.6.0

2 years ago

1.4.2

2 years ago

1.4.1

2 years ago

1.4.0

2 years ago

1.2.2

2 years ago

1.2.1

2 years ago

2.0.2

2 years ago

2.0.1

2 years ago

2.0.0

2 years ago

1.9.4

2 years ago

1.9.3

2 years ago

1.7.5

2 years ago

1.5.7

2 years ago

1.9.2

2 years ago

1.7.4

2 years ago

1.5.6

2 years ago

1.9.1

2 years ago

1.7.3

2 years ago

1.5.5

2 years ago

1.9.0

2 years ago

1.7.2

2 years ago

1.5.4

2 years ago

1.7.1

2 years ago

1.5.3

2 years ago

1.7.0

2 years ago

1.5.2

2 years ago

1.3.4

2 years ago

1.5.1

2 years ago

1.3.3

2 years ago

1.5.0

2 years ago

1.3.2

2 years ago

1.3.1

2 years ago

1.3.0

2 years ago

1.1.3

2 years ago

1.1.2

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago

0.1.0

2 years ago

0.0.6

2 years ago

0.0.5

2 years ago

0.0.4

2 years ago

0.0.3

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago