Open source is not easy, thanks for your support ❤ star concent^_^

The preface

In react, there is a top component that has a long life cycle. There is no time for the top component to be destroyed except for the unmountComponentAtNode interface to uninstall it and the user to close the browser TAB window. It is always with the application. So we make requests in the component’s componentDidMount function to get the configuration data on the server side and cache it for global use throughout the application.

For componentDidMount mounted by the routing system, we also make a request in the componentDidMount function to retrieve the page. If the state is managed by a store (such as Redux or Mobx) and the store state needs to be cleaned up when the page component is uninstalled, Will also choose to call the corresponding method in componentWillUnmount to clean up.

Of course, using the useEffect hook function is a one-step process for function components and is much simpler than using class components

function PageComp(){
  useEffect(() = >{
    /** is equivalent to componentDidMount calling */
    return () = >{
      /** equivalent to componentWillUnmount to do the corresponding cleanup */}}}, [])Copy the code

Experience with the current life cycle function

What about the elimination lifecycle mentioned in the title of this article? It seems that we can’t accomplish similar requirements without them, but before explaining that, let’s enumerate the current lifecycle experience issues.

Unable to share one set of logic

Type of component and function component is unable to do use a set of logical, 0 change type of components in the future will be a long period of time, this is we can’t avoid it, but the type of component and function component design idea using way lead to the life cycle of their function is completely different, so the Shared logic requires some modification

The initialization process is coupled to the component

The initialization process of promoted store state is still coupled to the component. It is important to note that we usually initialize the state of a store node in a top-level component lifecycle function. Both root and page components have the properties of top-level components. But writing the state initialization process for a store node in a component introduces some additional problems,

  • If another page component also needs to use the node data, additional checks are required to see that the state is properly initialized
  • This declaration cycle logic should be carefully maintained when refactoring top-level components

Let’s take a look at how concent handles these issues and kills off lifecycle functions.

Unify logic using composite apis

Although the life cycles of class components and functions are declared and used in completely different ways, we can rely on composite apis to bridge this gap, so that both class components and function components truly act as UI vectors

Suppose you have the following two self-managed components, both of which have the same functionality: one is a class component

class ClsPageComp extends React.Component{
  state = {
    list: [].page: 1};componentDidMount(){
    fetchData();
  }
  componentWillUnmount(){
    /** clear up */
  }
  fetchData = () = > {
    const { page } = this.state;
    fetch('xxxx', { page }).then(list= > this.setState({ list }))
  }
  nextPage = () = > {
    this.setState({ page: this.page + 1 }, this.fetchData);
  }
  render() {
    /** ui logic */}}Copy the code

One is the function component

// Function components
function PageComp() {
  const [list, setList] = useState([]);
  const [page, setPage] = useState(1);

  const pageRef = useRef(page);
  pageRef.current = page;

  const fetchData = (page) = > {
    // fetch("xxxx", { page }).then((list) => setList(list));
  };

  const nextPage = () = > {
    const p = page + 1;
    setPage(p);
    fetchData(p);
  };

  useEffect(() = > {
    fetchData(pageRef.current);
    return () = > {
      /** clear up */}; } []);/** ui logic */
}
Copy the code

The two look completely different, and the tedious operation of using useRef to fix the target value in the function component to eliminate useEffect dependency missing warnings can be a big hurdle for new users.

Let’s take a look at how the combined API based on Setup, which is a normal function that provides only one parameter representing the current render context and supports returning a new object (usually a collection of methods) that can be retrieved from the render block via Settings. A component with a setup function is triggered only once when instantiated, so we can see that the above example changes to:

function setup(ctx) {
  const { initState, setState, state, effect } = ctx;
  initState({ list: [].page: 0 });

  const fetchData = (page) = > {
    fetch('xxxx', { page }).then(list= > setState({ list }))
  };

  effect(() = >{
    fetchData(state.page);
    return () = >{
       /** clear up */}; } []);return {
    nextPage: () = > {
      const p = page + 1;
      setState({ page: p }); fetchData(p); }}; }Copy the code

Data and methods can then be retrieved from the render context CTX in both the class component and the function component

import { register, useConcent } from 'concent';

@register({ setup })
class ClsComp extends React.Component {
  render() {
    const { state: { page, list }, settings: { nextPage } } = this.ctx;
    // ui logic}}function PageComp() {
  const {
    state: { page, list }, settings: { nextPage },
  } = useConcent({ setup });
  // ui logic
}
Copy the code

Eliminate the lifecycle with Lifecyle

Lifecyle. mounted and lifecyle.willUnmount can be used to completely decouple lifecyle.mounted and lifecyle.willUnmount. Concent maintains a module instance counter. So depending on this function you can precisely control the initialization timing of the module state.

lifecyle.mounted

Trigger when the first instance of the current module is mounted, and only trigger once, that is, when all instances of the module are destroyed, another instance is mounted, will not trigger

run({
  product: { 
    lifecycle: {
      mounted: (dispatch) = > dispatch('initState')}}})Copy the code

If the number of instances of the module ranges from 0 to 1, return false

lifecyle.willUnmount

Triggered when the last instance of the current module is destroyed, and only once, that is, when the module generates many more instances and then destroys them all

run({
  counter: { 
    lifecycle: {
      willUnmount: dispatch= > dispatch('clearModuleState'),}}})Copy the code

If the number of instances of the module changes to 0, it will be triggered

lifecyle.loaded

If the status of the module does not depend on whether components are mounted, the module can be directly configured as Loaded

run({
  counter: { 
    lifecycle: {
      loaded: (dispatch) = > dispatch('initState'),}}})Copy the code

Modified sample

After lifecyle is introduced, let’s take a look at how instances of the above function and class components look. First we define the product module

import { run } from 'concent';

run({
  product: {
    state: { list: [].page: 1 },
    reducer: {
      async initState() {
        /** init state logic */
      },
      clearState() {
        /** clear state logic */
      },
      async nextPage(payload, moduleState, ac) {
        const p = moduleState.page + 1;
        await ac.setState({ paeg: p });
        const list = await fetch('xxxx', { page: p });
        return{ list }; }},lifecycle: {
      mounted: dispatch= > dispatch('initState'),
      willUnmount: dispatch= > dispatch('clearState'),}}});Copy the code

Then we register the component as belonging to the Product module, and the component instance can call the product module’s methods and read its data.

import { register, useConcent } from 'concent';

@register({ module: 'product' })
class ClsComp extends React.Component {
  render() {
    const { state: { page, list }, 先生: { nextPage } } = this.ctx;
    // ui logic}}function PageComp() {
  const {
    state: { page, list }, 先生: { nextPage },
  } = useConcent({ module: 'product' });
  // ui logic
}
Copy the code

We can see that there is no setup at this point because we don’t need to define additional methods and data. We can still define setup when we need to define some non-module methods and data for the component

function setup(ctx) {
  const { initState, setState, state, effect } = ctx;
  initState({ xxxx: 'hey i am private' });
  effect(() = >{
   // This is equivalent to useEffect, when XXXX changes
   console.log(state.xxxx); },'xxxx']);

  return {
    changeXXX: (e) = > setState({xxxx: e.target.value}),
  };
}
Copy the code

Then the component assembles setup

import { register, useConcent } from 'concent';

@register({ module: 'product', setup })
class ClsComp extends React.Component {
  render() {
    const { state: { page, list }, 先生: { nextPage }, settings } = this.ctx;
    // ui logic}}function PageComp() {
  const {
    state: { page, list }, 先生: { nextPage }, settings,
  } = useConcent({ module: 'product', setup });
  // ui logic
}
Copy the code

conclusion

In summary, we can see that rather than eliminating lifecycle functions, we have moved and unified the definition entry of lifecycle functions, completely separating them from the definition of components, so that no matter how we refactor component code, we do not have to disturb the initialization process of the entire module state.

The appendix

Other articles on the topic of this issue

  • Introduction to composition apis
  • recoil vs concent

CloudBase CMS

Welcome to CloudBase CMS to create a one-stop cloud content management system. It is a Headless content management platform based on Node.js launched by cloud development, which provides rich content management functions. It is easy to install, easy to develop again, and closely integrated with the cloud development ecosystem. Help developers improve development efficiency.

Concent already has strong support for its admin backend, and the new admin interface is much more beautiful and thoughtful.

FFCreator

Welcome to FFCreator, which is a lightweight and flexible short video processing library based on Node.js. All you need to do is add a few pictures or video clips and a piece of background music to quickly create a cool video clip.

FFCreator is a lightweight and simple solution that requires few dependencies and a low machine configuration to start working quickly. And it simulates 90 percent of animate. CSS animation, you can easily turn web page side animation into video, really cool.