React source code analysis 18 (7) ------ Implementing event mechanism (onClick event)

Summary

In the previous article, we implemented the useState hook, but since the event mechanism was not implemented, we could only mount setState on the window.
This article is mainly to implement the event system, so as to implement setState through click events.

In React, although we bind the event to an element on JSX, the final executor is actually the outermost container.
In other words, React uses the bubbling mechanism to bubble all events to the outermost container, thereby creating synthetic events and executing the corresponding events.

So before implementing the event mechanism, we first modify the prepared JSX:

function App() {
    
    
  const [name, setName] = useState('kusi','key');
  const [age, setAge] = useState(20)
  const click1 = () => {
    
    
    console.log(name)
    setName(name + '1')
  }
  const click2 = () => {
    
    
    console.log(age)
    setAge(age + 1)
  }
  return jsx("div", {
    
    
    ref: "123",
    onClick: click1,
    children: jsx("span", {
    
    
      children: name + age,
      onClick: click2
    })
  });
}

1. Implement the initEvent method

As we just said, in React, the executor of the event is the outermost container, which means that we need to bind an event to the outermost container for initialization.

export const initEvent = (root, eventType) => {
    
    
  root.addEventListener(eventType, (e) => {
    
    
    dispatchEvent()
  })
}

And we can call initEvent at the beginning. The very beginning is in the createContainer method:

function createContainer(root) {
    
    
  initEvent(root, 'click')
  const hostRootFilber = new FilberNode(HostRoot, {
    
    }, '')
  return new FilberRootNode(root, hostRootFilber)
}

Here we first implement the click event.

2. Bind props to all DOM

Let’s think about it. All events must be in the Props of the corresponding component. If we want to get the corresponding event on the dom, we must synchronize the props attribute to the dom.
The real DOM is generated in the completeWork stage, so we need to implement a method to bind the props attribute to the dom:

function addPropsToDOM(element, props) {
    
    
  element['__props'] = props
}

In the completeWork phase, call this method:

export const completeWork = (filberNode) => {
    
    
  const tag = filberNode.tag
  switch (tag) {
    
    
    case HostComponent: {
    
    
      if(filberNode.stateNode !== null){
    
    
        //更新
        addPropsToDOM(filberNode.stateNode, filberNode.pendingProps)
      }else{
    
    
        completeHostComponent(filberNode)
      }
      break;
    }
function completeHostComponent(filberNode) {
    
    
  const type = filberNode.type;
  const element = document.createElement(type);
  addPropsToDOM(element, filberNode.pendingProps)
  filberNode.stateNode = element;
  const parent = filberNode.return;
  if(parent && parent.stateNode && parent.tag === HostComponent) {
    
    
    parent.stateNode.appendChild(element)
  }
  completeWork(filberNode.child)
}

At this point, you can print and see if the element in stateNode already has the __props attribute:

Insert image description here

3. Collect all events

Now all DOM has corresponding events, now we need to collect all events:
The collection process is to move the currently clicked element to the outermost container All events recorded.
So we need three parameters: the currently clicked element, container, and event type.

Because in React, there are two types of events, such as onClick and onClickCapture. So we use two collections to collect these two events.

function collectEvent(event, root, eventType) {
    
    
  const bubble = [];
  const capture = [];

  while(event !== root){
    
    
    const eventProps = event['__props'];
    if(eventType === 'click'){
    
    
      const click = eventProps['onClick'];
      const clickCapture = eventProps['onClickCapture'];
      if(click){
    
    
        bubble.push(click);
      }
      if(clickCapture){
    
    
        capture.unshift(clickCapture)
      }
    }
    event = event.parentNode;
  }
  return {
    
    bubble, capture}
}

Then we call it in dispatchEvent:

function dispatchEvent(root, eventType, e) {
    
    
  const {
    
    bubble, capture} = collectEvent(e.target, root, eventType)
  console.log(bubble, capture);
}

Let’s take a look at the print result:
Insert image description here
You can see that the method has been saved in the bubble.

4. Create synthetic event objects

Now that we have collected so many methods, it stands to reason that it is time to implement them. But there is a problem, we created bubble and capture. It is just used to imitate the browser's bubbling and capturing, that is, it is not a real bubbling capture.

It is root that ultimately executes all events, so we need to create a new event to replace the browser event.

In this method, we use a flag __stopPropgation to decide whether to bubble. If "e.stopPropgation" is called outside, we set this flag to true.

function createSyntheticEvent(e) {
    
    
  const syntheticEvent = e;
  syntheticEvent.__stopPropgation = false;
  const originStopPropgation = e.stopPropagation;

  syntheticEvent.stopPropagation = () => {
    
    
    syntheticEvent.__stopPropgation = true;
    if( originStopPropgation ) {
    
    
      originStopPropgation()
    }
  }
  return syntheticEvent;
}
}

Call in dispatchEvent:

function dispatchEvent(root, eventType, e) {
    
    
  const {
    
    bubble, capture} = collectEvent(e.target, root, eventType)
  console.log(bubble, capture);
  const se = createSyntheticEvent(e)
}

4.Event call

OK, now we have to go to the last step and call the event. We only need to traverse the events in bubble and capture. Now we implement a method:

function triggerEvent(paths, se) {
    
    
  for(let i=0; i< paths.length; i++) {
    
    
    paths[i].call(null, se);
    if(se.__stopPropgation) {
    
    
      break;
    }
  }
}

Then execute it in dispatchEvent:

function dispatchEvent(root, eventType, e) {
    
    
  const {
    
    bubble, capture} = collectEvent(e.target, root, eventType)
  const se = createSyntheticEvent(e);
  triggerEvent(capture,se);
  if(!se.__stopPropgation) {
    
    
    triggerEvent(bubble,se)
  }
}

Guess you like

Origin blog.csdn.net/weixin_46726346/article/details/132290001