Coding Style

Business architecture

From a business architecture perspective, it is recommended to write components in a “logical separation” style: components are responsible for “interaction + UI”, Hooks are equivalent to “logic + data”, keep state and UI separate as much as possible, “stateful components have no render, rendered components have no state”. In layman’s terms, component files with UI have no useState concept direction, so it’s a case by case.

The Custom Hooks format

The files are saved in the hooks folder in the page directory or the root directory of the common component, and the file naming style is named after the function names

  • Camelcase or kebabcase?

    Personally, I think it is better to use CamelCase for readability, but I will use camelcase for all project requirements.

  • All useState precursors (although the order may not affect use and execution, it is easy to read)

  • You must write deps

  • Return format, first the result, using {… state,… The format of actions}

    Advantages: Type inference, no-break change (subsequent changes have no effect where hooks were previously used)

  • Other formats:

    1. […state,…actions], personally preferred way, React official way

      Advantages: Simple, easy to alias

      Disadvantages: Break change

    2. [{…state}, {…action}], reduck like

      Advantages: no-break change

      Disadvantages:

      1. Need to rewrite the type, cannot automatically infer 2. Need aliasCopy the code

For the business, the ability not to generate break-change may have the effect of reducing the amount of code modification required for maintenance code and iteration. And can automatically infer the type, can reduce a lot of duplicate type writing, finally use {… state, … The format of actions}

Reduce the number of return values as much as possible, introvert, reduce the degree of coupling

Reduce the number of incoming values as much as possible, and manage the parameters yourself

For example, hooks to a list of data that can be loaded, limit, total, etc

const useSomeItems = () = >{...return{items, hasMore, loadMore, refresh}; }Copy the code

Learn more and think more about Hooks

An overview of

Code entry: the react/packages/react – the reconciler/SRC/ReactFiberHooks# 346: renderWithHooks

First, Hooks are implemented through closures. Centrally control the refresh execution of the Component under the current scope by storing variables under the current FiberNode. The general picture is as follows:

When executed, each hook function generates a hook object that records the value and change of the value. The whole is a linked list structure, each execution will get the next node of the current hook cursor, from which the stored value and update, callback function and so on. Therefore, each execution must be consistent with the function and order (no variation or judgment is allowed). The storage format is as follows:

Look again at the type of Hook structure. As you can see, the hook keeps a record of all data changes during the render, and stores the changes, not the results. The result of the update will be iterated one by one in the useHook of the next function execution. In this case, in addition to waiting for the completion of other events of the current function rendering, we also need to wait for the scheduler to judge according to the priority of the algorithm, and finally execute a new round of rendering. So data changes asynchronously.

type Update<S, A> = {|
  expirationTime: ExpirationTime,
  suspenseConfig: null | SuspenseConfig,
  action: A, // The update behavior can be either a direct assignment or a function relative to the previous value
  eagerReducer: ((S, A) = > S) | null.eagerState: S | null.// Predict the possible change of the value, if the value is consistent with the current value, stop applying for rendering (scheduleUpdateOnFiber)
  next: Update<S, A>, // List structure, pointing to the next update, a single execution may have multiple updatespriority? : ReactPriorityLevel,// Fiber algorithm priority flag
};

type UpdateQueue<S, A> = {|
  pending: Update<S, A> | null.// Cache waits for updates to be executed, added by dispatchAction (i.e., setState)
  dispatch: (A= > mixed) | null.// Bind the update function of the current value
  lastRenderedReducer: ((S, A) = > S) | null.lastRenderedState: S | null.// Records the result of the last update
};

export type Hook = {|
  memoizedState: any, // The current latest value
  baseState: any, // The initial value of this render execution
  baseQueue: Update<any, any> | null.// Update records of the current node in the last execution process. Synchronizes from the queue each time it is reexecuted
  queue: UpdateQueue<any, any> | null.// Update records of the current node
  next: Hook | null.// List structure, pointing to the next node
};
Copy the code

Schematic diagram of specific execution process:

Get to know these upper-class residents.


// The current execution domain corresponds to Fiber
let currentlyRenderingFiber: Fiber = (null: any);

// The node cursor of the currently rendered hooks that store the chain structure is the next node to use
// Before each execution, the memorizeSate of the last fiber is assigned, that is, the header of the last data link list
let currentHook: Hook | null = null;
// The node cursor of the chain structure for which hooks are needed for the next rendering is the next node to be used
let workInProgressHook: Hook | null = null;

// Whether there is any data update
let didScheduleRenderPhaseUpdate: boolean = false;

// Will there be any updates when the next rendering takes place
let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;

// Can be drawn from the number of times, only do dev mode hint, no constraint
// Only represents the number of component executions, not the actual number of renders
// You can easily get an error message in the following ways
// {count < 50 && setCount(e => e + 1)}
const RE_RENDER_LIMIT = 25;
Copy the code

EffectState

Side effects will perform more than in the chain of the hooks as the value stored in the table, in currentlyRenderingFiber. UpdateQueue saving a side effect in the linked list, and effectTag, mark current state of the Fiber have side effects. Tag is stored as a binary union. Judge DEPS, bind corresponding tags to currentlyRenderingFiber and Effect according to DEPS, and perform side effects when both tags are in line with the current life Cycle hook tag.

All side effects are executed after the functional function completes, and side effects are executed in code order (linked list order). Each trigger (mount, update, unmount, etc.) iterates through all effects once to determine whether the tag is executed. So write dePS and internal judgments as much as possible to minimize useless execution. Empty DEPS is not bound to mount and unmount. It is only the first time that all is executed and the last time that all is reclaimed. The update in between is not executed because dePS is unchanged.

Deps

Deps is the second parameter of the generic hooks that match whether updates are needed. Note that the values of the function (except for setState or useCallback which are built-in in hooks, etc.) are new every time, writing them into dePS means that they are executed every time. Array. length, a.b === c, array.length, a.b === c.

FAQ

Q: Why do hooks write at the bottom of this function without judgment?

A: Because each hooks are A node of A chain structure in A function, the corresponding hooks are read and written sequentially during function execution.

Q: When should YOU use useMemo, useCallback?

A: Development first consider not using these hooks first. Because this is part of the performance optimization, adding it will only increase the mental burden of reporting errors, and will not reduce the existing bugs. And both use itself has a certain performance cost

useMemo

When a page has multiple components, updating part of the data does not require a child component to re-execute, or a value update. UseMemo can be used to reduce the number of components to perform repeated rendering. Unless the data has a certain calculation cost, it does not need to store the value. Even direct const/let Hooks execute once by default, so the values and functions are new each time

useCallback

You can make sure that useMemo uses functions that are updated as needed rather than every time. Functions are new every time for deps diff (object.is). You need to consider whether you can only listen for data changes. If not, should you bind useMemo at a higher level? UseCallback is only used when data and events are not updated synchronously. Generally, it is not needed. You just need to listen for data changes. If you must use it, think about whether it is necessary and whether it can be designed so that it is not needed

Memo

In addition to being used separately, the memo is used in conjunction with the memo, so that the pure presentation component is not re-executed when the parent component is updated.

Q: How do I debug the React source code?

A:

  1. Configuration webpack
const { DefinePlugin } = require('webpack')...resolve: {
    alias: {
      react: '/path/to/react/packages/react'.'react-dom': '/path/to/react/packages/react-dom'.shared: '/path/to/Packages/react/packages/shared'.'react-reconciler': '/path/to/Packages/react/packages/react-reconciler'.'react-events': '/path/to/Packages/react/packages/events'.'legacy-events': '/path/to/Packages/react/packages/legacy-events'.scheduler: '/path/to/Packages/react/packages/scheduler',}},...plugins: [
    new DefinePlugin({
      __DEV__: true.__PROFILE__: true.__UMD__: true.__EXPERIMENTAL__: true,}),],...module: {
    rules: [{test: /\.js? $/,
        loader: 'babel-loader'.exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-env'.'@babel/preset-flow'.'@babel/preset-react'],},},]}Copy the code
  1. Modify the source code

Some parts need to be modified before they can be executed properly

packages/react-reconciler/src/ReactFiberHostConfig.js

// import invariant from 'shared/invariant';

// invariant(false, 'This module must be shimmed by a specific renderer.');

export * from './forks/ReactFiberHostConfig.dom';
Copy the code

packages/scheduler/src/SchedulerHostConfig.js

// throw new Error('This module must be shimmed by a specific build.');

export * from './forks/SchedulerHostConfig.default.js';
Copy the code

packages/shared/ReactSharedInternals.js

// const ReactSharedInternals =
// React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;

import ReactSharedInternals from '.. /react/src/ReactSharedInternals';
Copy the code

packages/shared/invariant.js

export default function invariant(condition, format, a, b, c, d, e, f) {
  if (condition) return;
  console.error(format);
}
Copy the code
  1. Use the React
// I tried @babel/preset-react here, so preset is used anyway
import * as React from 'react'
Copy the code

Refers

Jancat. Making. IO/post / 2019 / t…