为什么要用框架


一直在用Vue,但是也没有想过框架的出现是为了解决什么痛点?

实现一个点赞功能

首先,实现一个简单的点赞功能,不考虑使用任何框架。

<body>
  <div class='wrapper'>
    <button class='like-btn'>
      <span class='like-text'>点赞</span>
      <span>👍</span>
    </button>
  </div>

  <script>
    // 实现一个简单的点赞按钮,点击按钮的同时按钮的文字会变换。demo1难以复用
    const button = document.querySelector('.like-btn')
    const buttonText = button.querySelector('.like-text')
    let isLiked = false
    button.addEventListener('click', () => {
      isLiked = !isLiked
      if (isLiked) {
        buttonText.innerHTML = '取消'
      } else {
        buttonText.innerHTML = '点赞'
      }
    }, false)
  </script>
</body>

以上代码实现了点赞的功能,但是有一个问题——难以复用。

结构复用

经过改造,我们也仅仅只能复用html部分,并且没有添加事件

<body>
  <div class='wrapper'></div>

  <script>
    // 仅仅可以复用html部分,没有添加事件
    class LikeButton {
      render(){
        return `
          <button id='like-btn'>
            <span class='like-text'>赞</span>
            <span>👍</span>
          </button>
        `
      }
    }

    const wrapper = document.querySelector('.wrapper')
    const likeButton1 = new LikeButton()
    wrapper.innerHTML = likeButton1.render()

    const likeButton2 = new LikeButton()
    wrapper.innerHTML += likeButton2.render()
  </script>
</body>

添加事件

虽然添加了click事件,但是只能输出固定的click。我们需要点击按钮,改变文字

<body>
  <div class='wrapper'></div>

  <script>
    // 添加了click事件,但是只能打印固定的click
    // ::String => ::Document
    const createDOMFromString = (domString) => {
      const div = document.createElement('div')
      div.innerHTML = domString
      return div
    }

    class LikeButton {
      render(){
        this.el = createDOMFromString(`
          <button class='like-button'>
            <span class='like-text'>点赞</span>
            <span>👍</span>
          </button>
        `)
        this.el.addEventListener('click', () => console.log('click'), false)
        return this.el
      }
    }

    const wrapper = document.querySelector('.wrapper')

    const likeButton1 = new LikeButton()
    wrapper.appendChild(likeButton1.render())

    const likeButton2 = new LikeButton()
    wrapper.appendChild(likeButton2.render())
  </script>
</body>

修改文字

点击按钮,会修改文字,但是changeLikeText方法频繁的操作dom,影响性能

<body>
  <div class='wrapper'></div>

  <script>
    // 点击按钮会执行changeLikeText方法,但是changeLikeText方法里面在不停的操作dom,影响性能
    // ::String => ::Document
    const createDOMFromString = (domString) => {
      const div = document.createElement('div')
      div.innerHTML = domString
      return div
    }

    class LikeButton {
      constructor () {
        this.state = { isLiked: false }
      }

      changeLikeText () {
        const likeText = this.el.querySelector('.like-text')
        this.state.isLiked = !this.state.isLiked
        likeText.innerHTML = this.state.isLiked ? '取消' : '点赞'
      }

      render(){
        this.el = createDOMFromString(`
          <button class='like-button'>
            <span class='like-text'>点赞</span>
            <span>👍</span>
          </button>
        `)
        this.el.addEventListener('click', this.changeLikeText.bind(this), false)
        return this.el
      }
    }

    const wrapper = document.querySelector('.wrapper')

    const likeButton1 = new LikeButton()
    wrapper.appendChild(likeButton1.render())

    const likeButton2 = new LikeButton()
    wrapper.appendChild(likeButton2.render())
  </script>
</body>

性能优化

到这一步骤,基本已经大体形成,我们可以做更进一步的优化。

<body>
  <div class='wrapper'></div>

  <script>
    // 点击按钮执行changeLikeText方法,该方法值会调用setState方法,setState方法会重新给satate赋值,并且修改虚拟html。最终只会操作一次dom
    // ::String => ::Document
    const createDOMFromString = (domString) => {
      const div = document.createElement('div')
      div.innerHTML = domString
      return div
    }

    class LikeButton {
      constructor () {
        this.state = { isLiked: false }
      }

      setState (state) {
        const oldEl = this.el
        this.state = state
        this.el = this.render()
        if (this.onStateChange) this.onStateChange(oldEl, this.el)
      }

      changeLikeText () {
        this.setState({
          isLiked: !this.state.isLiked
        })
      }

      render () {
        this.el = createDOMFromString(`
          <button class='like-btn'>
            <span class='like-text'>${this.state.isLiked ? '取消' : '点赞'}</span>
            <span>👍</span>
          </button>
        `)
        this.el.addEventListener('click', this.changeLikeText.bind(this), false)
        return this.el
      }
    }

    const wrapper = document.querySelector('.wrapper')

    const likeButton = new LikeButton()
    wrapper.appendChild(likeButton.render()) // 第一次插入 DOM 元素
    likeButton.onStateChange = (oldEl, newEl) => {
      wrapper.insertBefore(newEl, oldEl) // 插入新的元素
      wrapper.removeChild(oldEl) // 删除旧的元素
    }
  </script>
</body>

进一步优化

setState和_renderDOM封装成Component类,便于其他组件使用

<body>
  <div class='wrapper'></div>

  <script>
    // 把setState和_renderDOM方法进行抽离,修改dom的操作封装成方法
    // ::String => ::Document
    const createDOMFromString = (domString) => {
      const div = document.createElement('div')
      div.innerHTML = domString
      return div
    }

    const mount = (component, wrapper) => {
      wrapper.appendChild(component._renderDOM())
      component.onStateChange = (oldEl, newEl) => {
        wrapper.insertBefore(newEl, oldEl)
        wrapper.removeChild(oldEl)
      }
    }

    class Component {
      setState (state) {
        const oldEl = this.el
        this.state = state
        this._renderDOM()
        if (this.onStateChange) this.onStateChange(oldEl, this.el)
      }

      _renderDOM () {
        this.el = createDOMFromString(this.render())
        if (this.onClick) {
          this.el.addEventListener('click', this.onClick.bind(this), false)
        }
        return this.el
      }
    }

    class LikeButton extends Component {
      constructor () {
        super()
        this.state = { isLiked: false }
      }

      onClick () {
        this.setState({
          isLiked: !this.state.isLiked
        })
      }

      render () {
        return `
          <button class='like-btn'>
            <span class='like-text'>${this.state.isLiked ? '取消' : '点赞'}</span>
            <span>👍</span>
          </button>
        `
      }
    }

    const wrapper = document.querySelector('.wrapper')

    mount(new LikeButton(), wrapper)
  </script>
</body>

自定义配置

组件可通过props传参来自定义配置

<body>
  <div class='wrapper'></div>

  <script>
    // 组件可以自定义配置 通过props
    // ::String => ::Document
    const createDOMFromString = (domString) => {
      const div = document.createElement('div')
      div.innerHTML = domString
      return div
    }

    const mount = (component, wrapper) => {
      wrapper.appendChild(component._renderDOM())
      component.onStateChange = (oldEl, newEl) => {
        wrapper.insertBefore(newEl, oldEl)
        wrapper.removeChild(oldEl)
      }
    }

    class Component {
      constructor (props = {}) {
        this.props = props
      }

      setState (state) {
        const oldEl = this.el
        this.state = state
        this._renderDOM()
        if (this.onStateChange) this.onStateChange(oldEl, this.el)
      }

      _renderDOM () {
        this.el = createDOMFromString(this.render())
        if (this.onClick) {
          this.el.addEventListener('click', this.onClick.bind(this), false)
        }
        return this.el
      }
    }

    class LikeButton extends Component {
      constructor (props) {
        super(props)
        this.state = { isLiked: false }
      }

      onClick () {
        this.setState({
          isLiked: !this.state.isLiked
        })
      }

      render () {
        return `
          <button class='like-btn' style="background-color: ${this.props.bgColor}">
            <span class='like-text'>
              ${this.state.isLiked ? '取消' : '点赞'}
            </span>
            <span>👍</span>
          </button>
        `
      }
    }

    class RedBlueButton extends Component {
      constructor (props) {
        super(props)
        this.state = {
          color: 'red'
        }
      }

      onClick () {
        this.setState({
          color: 'blue'
        })
      }

      render () {
        return `
          <div style='color: ${this.state.color};'>${this.state.color}</div>
        `
      }
    }

    const wrapper = document.querySelector('.wrapper')

    mount(new LikeButton({ bgColor: 'red' }), wrapper)
  </script>
</body>

这样子循序渐进的看下来,就能体会到一个框架的诞生的个中缘由——并不是平白无故做出来的,而是为了解决一些问题,然后一点一点的优化,最终诞生的。