实现一个简化版的React——从组件的复用讲起

2019-02-18  本文已影响29人  HolidayPeng

一、如何实现组件的复用?
当我们需要一个按钮,点击之后提示当前时间,你可能会这么写:

  <body>
      <div class="container">
        <button class="btn"> What time is it? </button>
      </div>
      <script>
         document.querySelector('.btn').onclick = function() {
          alert(new Date());
        }
      </script>   
  </body>

当有很多地方都会用到这段代码时,你可能会想到要把这块功能封装成一个组件,放在不同的父组件里。那么问题来了,怎么把子组件中的HTML部分和JavaScript部分分别放在父组件的HTML和JavaScript中呢?你可能会这么写:

const DomStr = '<button class="btn"> What time is it? </button>';
const container = document.createElement('div');
container.innerHTML = DomStr;
container.addEventListener('click', function(){
  alert(new Date())
}, false);

我们进一步把这段代码封装成一个方法,方便在父组件中导入:

export function alertTimeButton(parentNode) {
  const domStr = '<button class="btn"> What time is it? </button>';
  const container = document.createElement('div');
  container.innerHTML = domStr;
  container.addEventListener('click', function(){
    alert(new Date())
  }, false);
  parentNode.appendChild(container);
}

这段代码做了四件事情:

  1. 把组件的HTML部分用字符串的形式保存在一个常量里;
  2. 创建一个DOM容器,把组件放进去;
  3. 绑定点击事件;
  4. 把装有组件的容器加到父组件的节点上。

如果我们需要编写其他功能的组件,第1、2、4条是不是可以提出来作为公共的部分呢?我们是不是可以写一个Component类,然后让功能组件继承这个类呢?

class Component {
  render (domStr, container) {
    const container = document.createElement('div');
    container.innerHTML = domStr;
    parentNode.appendChild(container);
}
class AlertTimeButton extends Component {
  constructor() {
    super();
  }
  alertTime() {
    alert(Date.now());
  }
  this.render(`<button id="btn" onclick="${this.alertTime.bind(this)}"> What time is it? </button>`, document.querySelector('.root'));
}

这样每次需要复用这个组件的时候就把它实例化,需要几个就实例化几个。
以上是针对简单组件的封装,当遇到父子组件嵌套和数据传递的时候,我们又该怎么做呢?

二、如何实现组件间数据的共享?
我们把上面的需求改一下:

  1. 在父组件中显示当前时间;
  2. 点击父组件,更新当前时间;
  3. 点击父组件下的子组件按钮,弹窗提示父组件中显示的时间。
    于是有下面的结构:
        class Btn extends Component {
          constructor(props) {
            super(props);
          }
          alertTime() {
            alert(this.props.now);
          }
          render() {
            return `<button class="btn"> What time is it? </button>`;
          }
          componentDidMount() {
            document.querySelector('.btn').addEventListener('click', this.alertTime.bind(this), false)
          }
        }
        class Timer extends Component {
          constructor() {
            super();
            this.state = {
              now: Date.now()
            }
          }
          updateTime() {
            this.setState({
              now: Date.now()
            });
          }
          componentDidMount() {
            document.querySelector('.area').addEventListener('click', this.updateTime.bind(this), false)
          }
          render() {
            return (
              `<div class="area"> It's ${this.state.now} 
                  <Btn now="${this.state.now}"/>
               </div>`;
            )
          }
        }

写到这里我们会遇到几个问题:

  1. 怎么把字符串模板中的组件和参数解析出来?
  2. 如何在state发生改变的时候重新渲染组件?
  3. 生命周期钩子,如componentDidMount在什么时候添加?

对于第一个问题,JSX已经帮我们做了:


JSX

对于第二个问题,最简单粗暴的方式,就是在setState方法中重新渲染一遍组件, 同时在渲染结束后添加生命周期钩子:

        class Component {
          constructor(props) {
            this.props = props;
          }
          renderDom(parentNode) {
            const container = document.createElement('div');
            container.innerHTML = this.render();
            parentNode.appendChild(container)
            if (this.componentDidMount) this.componentDidMount()
          }
          setState(obj) {
            Object.keys(obj).forEach(key =>  {
              if (this.state[key] !== obj[key]) {
                this.state[key] = obj[key]; 
                this.renderDom(parentNode);
              }
            })
          }
        }
       

这样当父组件连同子组件一起被重新渲染后,传递给子组件的props自然也被更新了。

上一篇下一篇

猜你喜欢

热点阅读