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:
-
[…state,…actions], personally preferred way, React official way
Advantages: Simple, easy to alias
Disadvantages: Break change
-
[{…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:
- 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
- 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
- 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…