preface

Today is the fifth day of learning React. My first small goal is to implement all functions of the todo manifest software on the Web side by one-for-one!

Everything written before will be in the preface:

📦 repository links to the React-Todo Gitee repository

💻 Online preview Effect React-Todo development progress

Create React App+Tailwind CSS +Material UI

# 👀 learn React from the ground up next day ~React configure Eslint+ router-dom

# 👀 Learn React from scratch 3 days ~React date selector component development +Dayjs use

# 👀 Start from scratch learn React day 4 ~📆 implement a nice popover calendar component

Today’s task

React Hook: How to React Hook: How to React Hook

Of course, the premise of performance optimization is to reduce unnecessary loss under the condition that the function is intact. Excessive optimization is not advisable

Before today’s article, please check the link of the react new document. The overall document structure is simpler and clearer, and there are many more cases. Thanks for the recommendation of @half drunk moon film ~

To optimize

First of all, we will print the rendering of the components. I will print the functions of layout, DatePicker time selector component, DatePopover popover component and Calendar Calendar component, which are nested with each other.

Optimization goes into repeat rendering for the first time

Enter the console page for the first time and the result is as follows:

As you can see, when entering the page, normally all components should be rendered once, but now each component is rendered twice, so we start to troubleshoot the problem.

DatePicker time picker component code is as follows:

// DatePicker.jsx

 / /... more
export default function DatePicker() {
  console.log("DatePicker starts rendering")

  // The currently selected date
  const [activeDate, setDate] = useState(dayjs())
  useEffect(() = > {
    setWeek(getThisWeek())
  }, [activeDate])

  // An array of date objects for seven days of the week
  const getThisWeek = () = > {
    return Array.from({ length: 7 }).map((item, index) = > {
      return activeDate.isoWeekday(index + 1)})}const [thisWeek, setWeek] = useState(getThisWeek())

  // Check if this is the selected date
  const isActive = (item) = > item.date() === activeDate.date()

  const toToday = () = > {
    // Jump to today
    setDate(dayjs())
    setWeek(getThisWeek())
  }

  const toLastWeek = () = > {
    // Displays the date of the previous week
    const lastWeek = thisWeek.map((item, index) = > {
      return dayjs(item).isoWeekday(index - 6)
    })
    setDate(dayjs(activeDate).subtract(7."d"))
    setWeek(lastWeek)
  }

  const toNextWeek = () = > {
    // Displays the date of the following week
    const nextWeek = thisWeek.map((item, index) = > {
      return dayjs(item).isoWeekday(index + 8)
    })
    setDate(dayjs(activeDate).add(7."d"))
    setWeek(nextWeek)
  }

  const [anchorEl, setAnchorEl] = useState(null)
  const showDatePopover = () = > {
    const datePickerTarget = document.getElementById("date-picker")
    setAnchorEl(datePickerTarget)
  }
  return (
    <div className="p-5 flex items-center ">/ /... more</div>)}Copy the code

The DatePicker time picker component has two variables activeDate and thisWeek defined by useState. If one of these variables changes, the component will rerender.

In order to trigger the change of these two variables, it must be modified by the method returned by useState. The code in the following part does not execute these two methods directly, so the problem should be useEffect. I add a print in useEffect to check the effect

useEffect(() = > {
    console.log("ActiveDate change")
    setWeek(getThisWeek())
  }, [activeDate])
Copy the code

Print result:

Sure enough, the first execution triggers the useEffect callback, which in turn triggers setWeek(getThisWeek()), causing the component to re-render.

This is because getThisWeek is a function that gets the new date of the week based on the new activeDate each time it is executed. Since the date object generated by DayJS is accurate to the millisecond, it returns a different value for each execution even in the same week. What we need to do is change getThisWeek directly to a variable so that setWeek is not triggered causing the component to refresh.

//old
const getThisWeek = () = > {
    return Array.from({ length: 7 }).map((item, index) = > {
      return activeDate.isoWeekday(index + 1)})}// new
  const getThisWeek = Array.from({ length: 7 }).map((item, index) = > {
    return activeDate.isoWeekday(index + 1)})Copy the code

Let’s look at the console again, as shown below:

Optimize click date repeat rendering

OK With that done, let’s take a look at the print of a selected new date, as shown below

How do I fix the fact that every time I click on a date, the component refreshes twice?

{thisWeek.map((item) = > {
  return (
    <div
      className={`flex items-center justify-center cursor-pointer w-7 h-7 rounded-full mx-1The ${isActive(item)? "bg-primary"
          : item.isToday()? "bg-gray-200"
          : "hover:bg-gray-200` "}}key={item}
      size="small"
      onClick={()= > {
        setDate(item)
      }}
    >
      <span className="text-sm">{item.isToday() ? "Today" : item.date()}</span>
    </div>)})}Copy the code

The change in the activeDate variable triggers the useEffect callback setWeek(getThisWeek()). So the component is re-rendered. Of course this step is fine, but if I choose the date of the week there is no need to hold setWeek(getThisWeek()) to create a new week array.

UseEffect = activeDate.isoweek (); activeDate.isoweek (); activeDate.isoweek (); When useEffect is triggered each time, the system checks whether the selected date is the same week after modification and before modification. If it is the same week, the callback does not need to be triggered.

// old
useEffect(() = > {
    setWeek(getThisWeek)
  }, [activeDate])
  
//new
useEffect(() = > {
    setWeek(getThisWeek)
  }, [activeDate.day()])
Copy the code

If we look at the console print, we can see that each selected date is only rendered once, as shown below:

Optimized calendar repeat rendering

Let’s first look at the printing of the unfolding calendar, as shown below:

As you can see, there is no problem with the expansion, the parent component rerenders the calendar, and the calendar also performs the rendering, but what if we close the calendar? The diagram below:

After closing the calendar, the calendar performs another rendering, which is a repeat rendering. I’ve already closed it and I don’t need it to update the data. The calendar is rendered again when closed because of the basisReact component rendering mechanism.When the parent component is re-rendered, the child component is also re-rendered.

What if you want to avoid this situation? We can use react. memo to solve this problem, as explained below

If your component is rendering the same props, you can improve the performance of the component by wrapping it in a react.Memo call to remember the component’s rendering results. This means that in this case React will skip the render component and simply reuse the results of the last render.

React.memo only checks the props changes. If a function component is wrapped in react. Memo and its implementation has a Hook for useState, useReducer, or useContext, it will still rerender when state or context changes.

In short, the react. memo wrapped child function will only be re-rendered if the received parameters change. If the received parameters do not change, the parent component will not be re-rendered even if the parent component is refreshed

The specific implementation is also very simple, the core code is as follows:

import React, { useState } from "react"
function Calendar(props) {
    // ...
}
export default memo(Calendar)
Copy the code

Use the Memo to wrap the Calendar component and expose it. Let’s take a look at the modified console output

There will be no extra rendering this time around

Optimized jump today button

Finally, there is a little sun button on the right. If you click on the little sun, the toToday method will be executed to jump toToday, but if the current selected date is today, the rendering will be repeated, as shown below

This step is also easy, modify toToday method and add a judgment, I used the isSame method provided by DayJS to determine, remember to pass in the second parameter to set the granularity to day, the code is as follows

const toToday = () = > {
    // Jump to today
    if (dayjs().isSame(activeDate,"day")) {
      setDate(dayjs())
      setWeek(getThisWeek)
    }
  }
Copy the code

conclusion

Because the amount of code is still relatively small, the points that can be optimized are still relatively few, that is, I write hook for the first time to find so many places can be optimized 😭

This time, useMemo and useCallback hooks are not used. In the future, we will learn more about the use of these two methods when there is a large amount of code.

React delivers more performance trade-offs to developers through apis than VUE, and requires more framework proficiency and development capabilities. Many of these challenges require development experience to feel comfortable