This is the seventh day of my participation in the More text Challenge. For details, see more text Challenge
Why platinum? Because there is still a long way to go. This article only implements the simple version of React, referring to the basic functions of React 16.8, including virtual DOM, Fiber, Diff algorithm, functional components, hooks, etc.
One, foreword
This article is based on pomb.us/build-your-… Implement a simple version of React.
B station -React source code, you are in the first layer.
The simulated version is React 16.8.
The following functions will be implemented:
- CreateElement (virtual DOM);
- Render.
- Concurrent mode;
- Fibers;
- Render and Commit Phases;
- Coordination (Diff algorithm);
- Function component;
- Hooks;
Now for dinner, read on.
2, preparation,
1. React Demo
Let’s start with a simple React Demo that looks like this:
const element = <div title="foo">hello</div>
const container = document.getElementById('container')
ReactDOM.render(element, container);
Copy the code
See reactDemo for the complete source code for this example
Open reactdemo.html in your browser and it looks like this:
We need to implement our React, so we need to know what the code above does.
1.1 element
Const Element =
is actually a JSX syntax.
React explains the JSX as follows:
JSX is a JavaScript syntax extension. It is similar to a template language, but it has the full power of JavaScript. JSX will eventually be compiled by Babel as a function call called react.createElement ().
Compile const Element =
online with Babel.
Const element =
const element = React.createElement("div", {
title: "foo"
}, "hello");
Copy the code
Let’s see what the React. CreateElement object actually looks like.
Try printing in demo:
const element = <div title="foo">hello</div>
console.log(element)
const container = document.getElementById('container')
ReactDOM.render(element, container);
Copy the code
You can see the output element as follows:
To simplify element:
const element = {
type: 'div'.props: {
title: 'foo'.children: 'hello'}}Copy the code
To summarize, React. CreateElement actually generates an Element object with the following properties:
- Type: the tag name
- props
- Title: indicates the tag attribute
- Children: the child nodes
1.2 render
Reactdom.render () adds the element to the DOM node with the ID container. Next we’ll simply write a method instead of reactdom.render ().
- Create a node with the tag element.
const node = document.createElement(element.type)
Copy the code
- Set the title of the node to element.props.
node["title"] = element.props.title
Copy the code
- Create an empty text node text;
const text = document.createTextNode("")
Copy the code
- Set the nodeValue of the text node to element.props. Children;
text["nodeValue"] = element.props.children
Copy the code
- Add text node to node;
node.appendChild(text)
Copy the code
- Add node to Container
container.appendChild(node)
Copy the code
See reactDemo2 for the complete source code for this example
Run the source code, and the result is the same as when introducing React:
Three,
Above, we simply replaced React. CreateElement and Reactdom. render methods by simulating React, and then we will really start to implement various functions of React.
1. CreateElement (virtual DOM)
CreateElement creates an Element object with the following structure:
// Virtual DOM structure
const element = {
type: 'div'./ / tag name
props: { // Node attributes, including children
title: 'foo'./ / the title attribute
children: 'hello' // Child nodes, note: this should actually be an array structure to help us store more child nodes}}Copy the code
Based on the structure of the element, the createElement function is designed. The code is as follows:
/** * Create virtual DOM structure *@param {type} Tag name *@param {props} Property object@param {children} Child node *@return {element} Virtual DOM * /
function createElement (type, props, ... children) {
return {
type,
props: {
...props,
children: children.map(child= >
typeof child === 'object'
? child
: createTextElement(child)
)
}
}
}
Copy the code
When children are non-objects, we should create a textElement element as follows:
/** * Create text node *@param {text} The text value *@return {element} Virtual DOM * /
function createTextElement (text) {
return {
type: "TEXT_ELEMENT".props: {
nodeValue: text,
children: []}}}Copy the code
Let’s try this with the following code:
const myReact = {
createElement
}
const element = myReact.createElement(
"div",
{ id: "foo" },
myReact.createElement("a".null."bar"),
myReact.createElement("b"))console.log(element)
Copy the code
See reactDemo3 for the complete source code for this example
The resulting Element object looks like this:
const element = {
"type": "div"."props": {
"id": "foo"."children": [{"type": "a"."props": {
"children": [{"type": "TEXT_ELEMENT"."props": {
"nodeValue": "bar"."children": []}}]}}, {"type": "b"."props": {
"children"[]}}]}}Copy the code
JSX
In fact, when we develop with React, we don’t create components like this:
const element = myReact.createElement(
"div",
{ id: "foo" },
myReact.createElement("a".null."bar"),
myReact.createElement("b"))Copy the code
Instead, through JSX syntax, the code looks like this:
const element = (
<div id='foo'>
<a>bar</a>
<b></b>
</div>
)
Copy the code
In myReact, we can use JSX syntax by adding comments that tell Babel to translate the function we specify, as follows:
/ * *@jsx myReact.createElement */
const element = (
<div id='foo'>
<a>bar</a>
<b></b>
</div>
)
Copy the code
See reactDemo4 for the complete source code for this example
2. render
The render function helps us add the Element to the real node.
It will be achieved in the following steps:
- Create a DOM node of type Element. type and add it to the container.
/** * Add virtual DOM to real DOM *@param {element} Virtual DOM *@param {container} Real DOM * /
function render (element, container) {
const dom = document.createElement(element.type)
container.appendChild(dom)
}
Copy the code
- Add both element.children to the DOM node;
element.props.children.forEach(child= >
render(child, dom)
)
Copy the code
- Special processing of text nodes;
const dom = element.type === 'TEXT_ELEMENT'
? document.createTextNode("")
: document.createElement(element.type)
Copy the code
- Add the element’s props attribute to the DOM;
const isProperty = key= >key ! = ="children"
Object.keys(element.props)
.filter(isProperty)
.forEach(name= > {
dom[name] = element.props[name]
})
Copy the code
We have implemented the function of rendering JSX to the real DOM. Let’s try it out. The code is as follows:
const myReact = {
createElement,
render
}
/ * *@jsx myReact.createElement */
const element = (
<div id='foo'>
<a>bar</a>
<b></b>
</div>
)
myReact.render(element, document.getElementById('container'))
Copy the code
See reactDemo5 for the complete source code for this example
The result is shown in the figure, and the output is successful:
3. Concurrent mode (requestIdleCallback)
Let’s see what happens to the child nodes in the render method:
/** * Add virtual DOM to real DOM *@param {element} Virtual DOM *@param {container} Real DOM * /
function render (element, container) {
/ / to omit
// Iterate over all the child nodes and render them
element.props.children.forEach(child= >
render(child, dom)
)
/ / to omit
}
Copy the code
The problem with this recursive call is that once the render starts, all nodes and their children will be rendered before the process ends.
When the DOM tree is large, the page is stuck during the rendering process, and interactive operations such as user input cannot be carried out.
The following steps can be taken to solve the above problems:
- Allow to interrupt the rendering work, if there is a higher priority work inserted, then temporarily interrupt the browser rendering, until the completion of the work, resume the browser rendering;
- Break down the rendering work into small units;
Use requestIdleCallback to solve the problem of allowing interrupts to render work.
Window. RequestIdleCallback will in the browser’s idle period called function to wait in line. This enables developers to perform background and low-priority work on the main event loop without affecting the delay of critical events such as animations and input responses.
Can view the document: document window. RequestIdleCallback detail
The code is as follows:
// Next unit of work
let nextUnitOfWork = null
/** * workLoop function *@param {deadline} Deadline */
function workLoop(deadline) {
// Whether to stop working loop functions
let shouldYield = false
// If there is a next unit of work and there is no other work of higher priority, the loop executes
while(nextUnitOfWork && ! shouldYield) { nextUnitOfWork = performUnitOfWork( nextUnitOfWork )// If the deadline is approaching, stop the working loop function
shouldYield = deadline.timeRemaining() < 1
}
// Notify the browser that the workLoop should be executed during idle time
requestIdleCallback(workLoop)
}
// Notify the browser that the workLoop should be executed during idle time
requestIdleCallback(workLoop)
// Execute the unit event and return the next unit event
function performUnitOfWork(nextUnitOfWork) {
// TODO
}
Copy the code
PerformUnitOfWork is used to execute unit events and return the next unit event, as described below.
4. fiber
RequestIdleCallback allows the browser to render units of work at idle time, avoiding the problem of overrendering and page stalling.
Note: requestIdleCallback is actually unstable and not recommended for use in production environments. This example is only used to simulate React ideas. React itself does not use requestIdleCallback to render units of work in the browser at idle time.
On the other hand, In order to make the render work separate into small units, React designed Fiber.
Each element is a fiber structure, and each fiber is a rendering unit.
So fiber is both a data structure and a unit of work.
Fiber is described below with a simple example.
Suppose you want to render such an Element tree:
myReact.render(
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>,
container
)
Copy the code
The generated fiber tree is shown in figure:
Orange is the child node, yellow is the parent node, and blue is the sibling node.
Each fiber has a link to its first child, its next sibling, and its parent. This data structure makes it easier to find the next unit of work.
The arrow in the image above also shows the rendering process of Fiber, which is described in detail below:
- Starting from root, find the first child div;
- Find the first child of the div h1;
- Find h1’s first child p;
- Find the first child node of P, if there is no child node, then find the next sibling node, find the sibling node a of P;
- Find the first child node of A, if there is no child node, and no sibling node, then find the next sibling node of its parent node, find the sibling node h2 of a’s parent node;
- Look for the first child of h2, can’t find it, look for the sibling, can’t find it, look for the sibling of the parent div, can’t find it, continue to look for the sibling of the parent div, find root;
- Step 6: The root node has been found, rendering is complete.
The following rendering process is implemented in code.
- Detach the part of Render that creates the DOM node into the creactDOM function;
/** * createDom Creates a DOM node@param {fiber} Fiber node *@return {dom} Dom node * /
function createDom (fiber) {
// If it is a text type, create an empty text node. If it is not a text type, create a node of type type
const dom = fiber.type === 'TEXT_ELEMENT'
? document.createTextNode("")
: document.createElement(fiber.type)
// isProperty indicates an attribute that is not children
const isProperty = key= >key ! = ="children"
// Iterates over props to add properties to the DOM
Object.keys(fiber.props)
.filter(isProperty)
.forEach(name= > {
dom[name] = fiber.props[name]
})
/ / returns the dom
return dom
}
Copy the code
- Set the first unit of work in Render as the fiber root node;
The fiber root node only contains the children attribute, and the value is the parameter fiber.
// Next unit of work
let nextUnitOfWork = null
/** * Add fiber to real DOM *@param {element} fiber
* @param {container} Real DOM * /
function render (element, container) {
nextUnitOfWork = {
dom: container,
props: {
children: [element]
}
}
}
Copy the code
- RequestIdleCallback renders the fiber when the browser is idle;
/** * workLoop function *@param {deadline} Deadline */
function workLoop(deadline) {
// Whether to stop working loop functions
let shouldYield = false
// If there is a next unit of work and there is no other work of higher priority, the loop executes
while(nextUnitOfWork && ! shouldYield) { nextUnitOfWork = performUnitOfWork( nextUnitOfWork )// If the deadline is approaching, stop the working loop function
shouldYield = deadline.timeRemaining() < 1
}
// Notify the browser that the workLoop should be executed during idle time
requestIdleCallback(workLoop)
}
// Notify the browser that the workLoop should be executed during idle time
requestIdleCallback(workLoop)
Copy the code
- Render fiber function performUnitOfWork;
/** * performUnitOfWork *@param {fiber} fiber
* @return {nextUnitOfWork} Next unit of work */
function performUnitOfWork(fiber) {
// TODO adds a DOM node
// TODO create a filber
// TODO returns the next unit of work (fiber)
}
Copy the code
4.1 Adding a DOM Node
function performUnitOfWork(fiber) {
// Create a DOM node for fiber if it does not have one
if(! fiber.dom) { fiber.dom = createDom(fiber) }// If fiber has a parent, add fiber.dom to the parent
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
}
Copy the code
4.2 new filber
function performUnitOfWork(fiber) {
// ~ ~ omit ~ ~
/ / child nodes
const elements = fiber.props.children
/ / index
let index = 0
// Last sibling node
let prevSibling = null
// Iterate over the child nodes
while (index < elements.length) {
const element = elements[index]
/ / create the fiber
const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null,}// Set the first child node to be a child of fiber
if (index === 0) {
fiber.child = newFiber
} else if (element) {
// The first child is set as the sibling of that node
prevSibling.sibling = newFiber
}
prevSibling = newFiber
index++
}
}
Copy the code
4.3 Return to the next unit of Work (fiber)
function performUnitOfWork(fiber) {
// ~ ~ omit ~ ~
// If there are children, return them
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
// If there is a sibling, return the sibling
if (nextFiber.sibling) {
return nextFiber.sibling
}
// Otherwise, continue through the while loop until root is found.
nextFiber = nextFiber.parent
}
}
Copy the code
We have implemented the ability to render fiber to the page, and the rendering process is interruptible.
Now try it out. The code looks like this:
const element = (
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>
)
myReact.render(element, document.getElementById('container'))
Copy the code
See reactDemo7 for the complete source code for this example
Output dom as expected, as shown in the figure below:
5. Render commit phase
Since we made the rendering process interruptible, we certainly didn’t want the browser to show the user a half-rendered UI.
The optimization of the render commit phase is as follows:
- Remove the logic in performUnitOfWork about adding children to parent nodes;
function performUnitOfWork(fiber) {
// Delete this paragraph
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
}
Copy the code
- Add a root variable to store the fiber root node.
/ / the root node
let wipRoot = null
function render (element, container) {
wipRoot = {
dom: container,
props: {
children: [element]
}
}
// The next unit of work is the root node
nextUnitOfWork = wipRoot
}
Copy the code
- When all fibers work, nextUnitOfWork is undefined and the real DOM is rendered.
function workLoop (deadline) {
/ / to omit
if(! nextUnitOfWork && wipRoot) { commitRoot() }/ / to omit
}
Copy the code
- New commitRoot function (commitRoot) to render the fiber tree to a real DOM recursion.
// Render the fiber tree as a real DOM;
function commitRoot () {
commitWork(wipRoot.child)
// This must be set to null, otherwise the workLoop will continue to execute when the browser is idle.
wipRoot = null
}
/** * performUnitOfWork *@param {fiber} fiber* /
function commitWork (fiber) {
if(! fiber)return
const domParent = fiber.parent.dom
domParent.appendChild(fiber.dom)
// Render the child node
commitWork(fiber.child)
// Render the sibling node
commitWork(fiber.sibling)
}
Copy the code
See reactDemo8 for the complete source code for this example
The running results of the source code are as follows:
6. Coordination (Diff algorithm)
When element is updated, the fiber tree before update needs to be compared with the fiber tree after update. After the comparison result is obtained, only the DOM node corresponding to the changed fiber is updated.
Through coordination, the number of operations on the real DOM is reduced.
1. currentRoot
Add currentRoot variable to save the alternate tree before the root node is updated. Add alternate attribute for fiber to save the alternate tree before the root node is updated.
let currentRoot = null
function render (element, container) {
wipRoot = {
/ / to omit
alternate: currentRoot
}
}
function commitRoot () {
commitWork(wipRoot.child)
currentRoot = wipRoot
wipRoot = null
}
Copy the code
2. performUnitOfWork
The new fiber logic in performUnitOfWork is extracted to reconcileChildren function.
/** * Coordinate child node *@param {fiber} fiber
* @param {elements} */ is a child node of fiber
function reconcileChildren (fiber, elements) {
// Used to count the index value of the child node
let index = 0
// Last sibling node
let prevSibling = null
// Iterate over the child nodes
while (index < elements.length) {
const element = elements[index]
/ / the new fiber
const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null,}// The first child node of fiber is its child node
if (index === 0) {
fiber.child = newFiber
} else if (element) {
// The other children of fiber are siblings of its first child
prevSibling.sibling = newFiber
}
// Assign the new newFiber to prevSibling, so you can easily add sibling nodes to newFiber
prevSibling = newFiber
// Index value + 1
index++
}
}
Copy the code
3. reconcileChildren
Comparing old and new fibers in reconcileChildren;
3.1 When old and new fiber types are the same
Keep the DOM, UPDATE props only, and set the effectTag to UPDATE;
function reconcileChildren (wipFiber, elements) {
// ~ ~ omit ~ ~
// oldFiber can be found in wipfiber.alternate
let oldFiber = wipFiber.alternate && wipFiber.alternate.child
while(index < elements.length || oldFiber ! =null) {
const element = elements[index]
let newFiber = null
// If the fiber type is the same
const sameType =
oldFiber &&
element &&
element.type == oldFiber.type
// Update props only if the types are the same
if (sameType) {
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE",}}// ~ ~ omit ~ ~
}
// ~ ~ omit ~ ~
}
Copy the code
3.2 When old and new fiber types are different and there are new elements
Create a new DOM node and set the effectTag to PLACEMENT;
function reconcileChildren (wipFiber, elements) {
// ~ ~ omit ~ ~
if(element && ! sameType) { newFiber = {type: element.type,
props: element.props,
dom: null.parent: wipFiber,
alternate: null.effectTag: "PLACEMENT",}}// ~ ~ omit ~ ~
}
Copy the code
3.3 When the types of old and new fibers are different and there are old fibers
Delete the old fiber and set the effectTag to DELETION.
function reconcileChildren (wipFiber, elements) {
// ~ ~ omit ~ ~
if(oldFiber && ! sameType) { oldFiber.effectTag ="DELETION"
deletions.push(oldFiber)
}
// ~ ~ omit ~ ~
}
Copy the code
4. deletions
Create a deletions array to store the fiber node to be deleted. When rendering DOM, traverse the deletions to delete the old fiber node.
let deletions = null
function render (element, container) {
/ / to omit
// Initialize the deletions array with render
deletions = []
}
// When rendering the DOM, traverse deletions to delete the old fiber
function commitRoot () {
deletions.forEach(commitWork)
}
Copy the code
5. commitWork
The effectTag of fiber is determined in commitWork and processed separately.
5.1 PLACEMENT
When the effectTag of fiber is PLACEMENT, it means that the fiber is added and the node is added to the parent node.
if (
fiber.effectTag === "PLACEMENT"&& fiber.dom ! =null
) {
domParent.appendChild(fiber.dom)
}
Copy the code
5.2 DELETION
When the effectTag of fiber is PLACEMENT, it means that the fiber is deleted and the node of the parent node is deleted.
else if (fiber.effectTag === "DELETION") {
domParent.removeChild(fiber.dom)
}
Copy the code
5.3 DELETION
When the effectTag of fiber is set to UPDATE, it means to UPDATE fiber and UPDATE the props property.
else if (fiber.effectTag === 'UPDATE'&& fiber.dom ! =null) {
updateDom(fiber.dom, fiber.alternate.props, fiber.props)
}
Copy the code
The updateDom function updates the props property based on the update type.
const isProperty = key= >key ! = ="children"
// Whether the attribute is new
const isNew = (prev, next) = > key= >prev[key] ! == next[key]// Is the old attribute
const isGone = (prev, next) = > key= >! (keyin next)
function updateDom(dom, prevProps, nextProps) {
// Delete old attributes
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach(name= > {
dom[name] = ""
})
// Update the new properties
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach(name= > {
dom[name] = nextProps[name]
})
}
Copy the code
In addition, add update and delete event properties to updateDom to make it easier to track updates to fiber events.
function updateDom(dom, prevProps, nextProps) {
// ~ ~ omit ~ ~
const isEvent = key= > key.startsWith("on")
// Delete old or changed events
Object.keys(prevProps)
.filter(isEvent)
.filter(
key= >! (keyin nextProps) ||
isNew(prevProps, nextProps)(key)
)
.forEach(name= > {
const eventType = name
.toLowerCase()
.substring(2)
dom.removeEventListener(
eventType,
prevProps[name]
)
})
// Register a new event
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach(name= > {
const eventType = name
.toLowerCase()
.substring(2)
dom.addEventListener(
eventType,
nextProps[name]
)
})
// ~ ~ omit ~ ~
}
Copy the code
Replace the logic for setting props in creactDOM.
function createDom (fiber) {
const dom = fiber.type === 'TEXT_ELEMENT'
? document.createTextNode("")
: document.createElement(fiber.type)
// Look at the duck
updateDom(dom, {}, fiber.props)
return dom
}
Copy the code
Create a new example with input form items and try updating Element with the following code:
/ * *@jsx myReact.createElement */
const container = document.getElementById("container")
const updateValue = e= > {
rerender(e.target.value)
}
const rerender = value= > {
const element = (
<div>
<input onInput={updateValue} value={value} />
<h2>Hello {value}</h2>
</div>
)
myReact.render(element, container)
}
rerender("World")
Copy the code
See reactDemo9 for the complete source code for this example
The output results are as follows:
7. Functional components
Let’s start with a simple example of a functional component:
MyReact does not yet support functional components. The following code will report an error when running, but this is only used as a comparison to how functional components are normally used.
/ * *@jsx myReact.createElement */
const container = document.getElementById("container")
function App (props) {
return (
<h1>hi~ {props.name}</h1>)}const element = (
<App name='foo' />
)
myReact.render(element, container)
Copy the code
Functional components differ from HTML tag components in two ways:
- The fiber function component has no DOM node;
- The children of the function component needs to be obtained after running the function;
The function component is implemented by the following steps:
- Modify performUnitOfWork to execute a fiber unit of work based on the fiber type.
function performUnitOfWork(fiber) {
// Whether it is a function component
const isFunctionComponent = fiber && fiber.type && fiber.type instanceof Function
// If it is a function component, execute updateFunctionComponent
if (isFunctionComponent) {
updateFunctionComponent(fiber)
} else {
// If it is not a function component, execute updateHostComponent
updateHostComponent(fiber)
}
/ / to omit
}
Copy the code
- Define the updateHostComponent function to execute non-functional components;
Nonfunctional components can pass fiber.props. Children directly as a parameter.
function updateHostComponent(fiber) {
if(! fiber.dom) { fiber.dom = createDom(fiber) } reconcileChildren(fiber, fiber.props.children) }Copy the code
- Define updateFunctionComponent and execute the function component.
The function component needs to run to get fiber.children.
function updateFunctionComponent(fiber) {
// fiber.type is the function component itself, and fiber.props is the function component parameter
const children = [fiber.type(fiber.props)]
reconcileChildren(fiber, children)
}
Copy the code
- Modify the commitWork function to accommodate fibers without DOM nodes.
4.1 Modify the domParent acquisition logic to keep searching upward through the while loop until the parent fiber with DOM nodes is found;
function commitWork (fiber) {
/ / to omit
let domParentFiber = fiber.parent
// If fiber.parent does not have a DOM node, continue to look for fiber.parent-parent-dom until there is a DOM node.
while(! domParentFiber.dom) { domParentFiber = domParentFiber.parent }const domParent = domParentFiber.dom
/ / to omit
}
Copy the code
4.2 Modify the logic of node deletion. When deleting a node, it is necessary to keep looking down until the sub-fiber with DOM node is found;
function commitWork (fiber) {
/ / to omit
// If the update type of fiber is delete, run the commitDeletion command
else if (fiber.effectTag === "DELETION") {
commitDeletion(fiber.dom, domParent)
}
/ / to omit
}
// Delete the node
function commitDeletion (fiber, domParent) {
// If the fiber has dom nodes, delete them directly
if (fiber.dom) {
domParent.removeChild(fiber.dom)
} else {
// If there is no DOM node in this fiber, continue to look for its children and delete them
commitDeletion(fiber.child, domParent)
}
}
Copy the code
Let’s take a look at the above example. The code is as follows:
/ * *@jsx myReact.createElement */
const container = document.getElementById("container")
function App (props) {
return (
<h1>hi~ {props.name}</h1>)}const element = (
<App name='foo' />
)
myReact.render(element, container)
Copy the code
See reactDemo10 for the complete source code for this example
The running results are as follows:
8. hooks
Let’s continue to add the ability to manage state to myReact. The expectation is that function components have their own state and can retrieve and update state.
A function component with a count function is as follows:
function Counter() {
const [state, setState] = myReact.useState(1)
return (
<h1 onClick={()= > setState(c => c + 1)}>
Count: {state}
</h1>)}const element = <Counter />
Copy the code
It is known that a useState method is required to retrieve and update the status.
Again, the rendering of the function component is based on the execution of the function component, so if the above Counter wants to update the count, it will execute the Counter function once for each update.
Through the following steps:
- New global variable wipFiber;
// The current unit of work is fiber
let wipFiber = null
function updateFunctionComponent(fiber) {
wipFiber = fiber
// Hook of current unit of work fiber
wipFiber.hook = []
/ / to omit
}
Copy the code
- New useState function;
// initial indicates the initial parameter, in this case initail=1
function useState (initial) {
// Whether there is an old hook, which stores the last updated hook
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hook
// Initialize the hook. The hook state is either the old hook state or the original hook state
const hook = {
state: oldHook ? oldHook.state : initial,
queue: [],}// Get all the actions from the old hook queue and apply them to the new hook state
const actions = oldHook ? oldHook.queue : []
actions.forEach(action= > {
hook.state = action(hook.state)
})
// Set the hook state
const setState = action= > {
// Add the action to the hook queue
hook.queue.push(action)
// Update the render
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot,
}
nextUnitOfWork = wipRoot
deletions = []
}
// Add the hook to the unit of work
wipFiber.hook = hook
// Returns the hook state and the function that sets the hook
return [hook.state, setState]
}
Copy the code
Let’s run the count component as follows:
function Counter() {
const [state, setState] = myReact.useState(1)
return (
<h1 onClick={()= > setState(c => c + 1)}>
Count: {state}
</h1>)}const element = <Counter />
Copy the code
See reactDemo11 for the complete source code for this example
The running results are as follows:
This section simply implements the myReact hooks functionality.
After all, react has many implementations worth studying and studying. I hope there will be more functions of React in the next installment.
conclusion
This paper refers to pomB.us for learning, and implements the custom React including virtual DOM, Fiber, Diff algorithm, functional components, hooks and other functions.
In the process of implementation, the editor has a general grasp of the basic terms and implementation ideas of React. Pomb. us is very suitable for beginners to learn materials, you can directly learn through pomB. us, but also recommended to follow this article step by step to realize the common functions of React.
Source code: Github source code.
Suggest following step by step knock, carry on practical practice.
I hope it can help you, thanks for reading ~
Don’t forget to encourage me with a thumbs-up, pen refill ❤️
The resources
-
Pomb. Us/build – your -…
-
Casson – B station -React source code, what floor are you on
-
Write a simple React by hand