GithubHelp home page GithubHelp logo

cpreact's Issues

踩坑日志: 关于 onClick 中的 this 指向

先来看下原生 JS 中,click 事件中 this 的指向

迷惑事例一:

有如下 html 代码,

<body>
  <input />
</body>

加上如下 JavaScript 脚本测试 this 指向:

var inputTest = document.getElementsByTagName('input')
inputTest[0].addEventListener('click', function {
  console.log(this) // 指向 input
})

inputTest[0].addEventListener('click', () => {
  console.log(this) // window
})

上述这段是为 dom 节点绑定事件的常见写法,但是 this 指向就很奇怪了,ES5 中指向了 , ES6 中却指向了 window。至于原因,其实是回调函数引起的坑(得看浏览器触发事件时的代码)。再加上箭头函数的特殊性所以产生以上迷惑的代码片段。

迷惑事例二:

const inputTest = document.getElementsByTagName('input')
class Test {
  click() {

  }

  testThis1() {
    inputTest[0].addEventListener('click', function() {
      console.log(this, this.click)
    })
  }

  testThis2() {
    inputTest[0].addEventListener('click', () => {
      console.log(this, this.click)
    })
  }
}

const test = new Test()
test.testThis1() // <input>  ƒ click()
test.testThis2() // Test {}    ƒ click()

此时,ES5 中指向了 , ES6 中却指向了 Test {};

总结:箭头函数和回调函数结合在一起有时候就是坑,比如原生事件里的 this 指向对我们使用者来说就是黑盒。

  • 在订阅事件的时候(框架内部)就执行 bind,这样子的话比较符合语义;
function setAttribute(dom, attr, value) {
...
else if (attr.match(/on\w+/)) {
    let eventName = attr.toLowerCase().substr(2)
    dom.addEventListener(eventName, value.bind(this))  // 在这里绑定
  }
...
}
class App extends Component {
  click() {
    console.log('click')
  }
  render() {
    return (
      <button onClick={function () { console.log(this.click) }}>Click Me2</button>  // 用 ES5 这样写就会报错,因为 this 指向 undefined(这里与 React 统一)
    )
  }
}
  • 另外在调用的时候进行函数的绑定(其它方式绑定也行);
class App extends Component {
  click() {
    console.log('click', this) // 'click' App{}
  }
  render() {
    return (
      <button onClick={this.click.bind(this)}>Click Me3</button> // 这里可以看出调用多个 bind 时,取第一个 bind 的值
    )
  }
}

函数从父组件传子组件的一些探索

PureComponent 下,验证函数以属性方式从父组件到子组件时候是否完成刷新

答案是'是'。通过分析,这个属于基本类型/引用类型的知识点,无需实验,靠逻辑就能想通。

关于 svg 渲染和显示

svg 元素在页面渲染出来了,但是页面中没有显示出来

class App extends Component {
  render() {
    return (
      <div>
        12345
        <svg>
          <circle cx={20} cy={20} r={20} fill='blue' />
        </svg>
      </div>
    )
  }
}

image

一些有用的测试用例

components
class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }

  click() {
    this.setState({
      count: ++this.state.count
    })
  }

  render() {
    return (
      <div>
        <button onClick={this.click.bind(this)}>Click Me!</button>
        <div>{this.state.count}</div>
      </div>
    )
  }
}

ReactDOM.render(
  <App name="count" />,
  document.getElementById('root')
)
state && props
// this case is to know the attr in the jsx `<A a={1} { ...obj } />` is converted to { a: 1, b: 2, c: 3 }

class A extends Component {
  render() {
    return (
      <div>{this.props.a + this.props.b + this.props.c}</div>
    )
  }
}

class B extends Component {
  render() {
    const obj = { b: 2, c: 3 }
    return (
      <div>
        <A a={1} { ...obj } />
      </div>
    )
  }
}

ReactDOM.render(
  <B />,
  document.getElementById('root')
)
life cycle
class A extends Component {
  componentWillReceiveProps(props) {
    console.log('componentWillReceiveProps')
  }

  render() {
    return (
      <div>{this.props.count}</div>
    )
  }
}

class B extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }

  componentWillMount() {
    console.log('componentWillMount')
  }

  componentDidMount() {
    console.log('componentDidMount')
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate', nextProps, nextState)
    return true
  }

  componentWillUpdate() {
    console.log('componentWillUpdate')
  }

  componentDidUpdate() {
    console.log('componentDidUpdate')
  }

  click() {
    this.setState({
      count: ++this.state.count
    })
  }

  render() {
    console.log('render')
    return (
      <div>
        <button onClick={this.click.bind(this)}>Click Me!</button>
        <A count={this.state.count} />
      </div>
    )
  }
}

ReactDOM.render(
  <B />,
  document.getElementById('root')
)
setState
class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
    this.click = this.click.bind(this)
  }

  click() {
    for (let i = 0; i < 10; i++) {
      this.setState({ // 在先前的逻辑中,没调用一次 setState 就会 render 一次
        count: ++this.state.count
      })
    }
  }

  render() {
    return (
      <div>
        <button onClick={this.click}>增加</button>
        <div>{this.state.count}</div>
      </div>
    )
  }
}
ref
class A extends Component {
  constructor() {
    super()
    this.state = {
      count: 0
    }
    this.click = this.click.bind(this)
  }

  click() {
    this.setState({
      count: ++this.state.count
    })
  }

  render() {
    return <div>{this.state.count}</div>
  }
}

class B extends Component {
  constructor() {
    super()
    this.click = this.click.bind(this)
  }

  click() {
    this.A.click()
  }

  render() {
    return (
      <div>
        <button onClick={this.click}>加1</button>
        <A ref={(e) => { this.A = e }} />
      </div>
    )
  }
}
PureComponent
// 测试用例:验证 state 浅比较
class B extends PureComponent {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
    this.click = this.click.bind(this)
  }

  click() {
    const state = Object.assign({}, this.state)

    this.setState({
      count: this.state.count + 1,
    })
  }

  render() {
    return (
      <div>
        <button onClick={this.click}>增加</button>
        <div>{this.state.count}</div>
      </div>
    )
  }
}

// 测试用例:验证 props 浅比较
class A extends PureComponent {
  render() {
    return (
      <div>{this.props.count.number}</div>
    )
  }
}

class B extends PureComponent {
  constructor(props) {
    super(props)
    this.state = {
      count: { number: 1 }
    }
  }

  click() {
    this.setState({
      count: { number: 1 }
    })
  }

  render() {
    return (
      <div>
        <button onClick={this.click.bind(this)}>Click Me!</button>
        <A count={ this.state.count } />
      </div>
    )
  }
}
性能优化实践 —— 避免不必要的渲染
import React from "react";
import ReactDOM from "react-dom";

class Todo extends React.Component {
  constructor() {
    super()
    this.state = {
      items: ['foo', 'bar'],
    }

    this.add = this.add.bind(this)
  }

  add(value) {
    const items = this.state.items.slice()
    items.push(value)

    this.setState({
      items,
    })
  }

  render() {
    console.log('renderParent')
    return (
      <div>
        <List items={this.state.items} />
        <Form add={this.add} />
      </div>
    )
  }
}

class List extends React.Component {
  render() {
    console.log('renderItem')
    return (
      <ul>
        {
          this.props.items.map(item =>
            <li key={item}>{item}</li>
          )
        }
      </ul>
    )
  }
}

class Form extends React.PureComponent {
  constructor() {
    super()
    this.state = {
      value: ''
    }

    this.handleChange = this.handleChange.bind(this)
  }

  handleChange({ target }) {
    this.setState({
      value: target.value,
    })
  }

  render() {
    console.log('renderForm')
    const { add } = this.props
    return (
      <div>
        <input
          value={this.state.value}
          onChange={this.handleChange}
        />
        <button onClick={() => add(this.state.value)}>+</button>
      </div>
    )
  }
}

ReactDOM.render(
  <Todo />,
  document.getElementById('root')
)

可以参考 性能优化实践之 —— 使用 why-did-you-update

Render Props
class PassState extends Component {
  constructor() {
    super()
    this.state = { name: 'muyy' }
  }

  render() {
    return (
      <div>
        {this.props.render(this.state)}
      </div>
    )
  }
}

class UseState extends Component {
  render() {
    const { state } = this.props
    return (<div>
      {state.name}
    </div>)
  }
}

class App extends Component {
  render() {
    return (
      <PassState render={(state) => (
        <UseState state={state} />
      )} />
    )
  }
}

踩坑日志:return () 后直接跟组件的情况会导致 diff 报错

案例如下:

function ppDecorate(WrappedComponent) {
  return class extends Component {
    constructor() {
      super()
      this.state = {
        value: ''
      }
      this.onChange = this.onChange.bind(this)
    }

    onChange(e) {
      this.setState({
        value: e.target.value
      })
    }

    render() {
      const obj = {
        onChange: this.onChange,
        value: this.state.value,
      }

      return (
        <WrappedComponent { ...this.props } { ...obj } />
      )
    }
  }
}

@ppDecorate
class B extends Component {

  render() {
    return (
      <div>
        <input { ...this.props } />
        <span>{ this.props.value }</span>
      </div>
    )
  }
}

HOC 探索 章节中,遇到一个坑,大致如下:

// 带上根元素,此时 diff 是正常的
return (
  <div>
    <WrappedComponent { ...this.props } { ...obj } />
  </div>
)

然而当没有带上根元素,却发现 diff 不正常。

return (
  <WrappedComponent { ...this.props } { ...obj } />
)

排查许久,发现 bug 出在这里 render.js 里的如下代码:

component.base = base // 将新得到的 dom 赋到 component 上

解决如下:

/**
 * 自定义组件渲染逻辑
 * @param {*} component
 */
function renderComponent(component) {
  // ...
  component.base = base        // 将新得到的 dom 赋到 component 上
  if (!_.isFunction(rendered.nodeName)) { // 只针对 return () 后不直接跟组件的情况才执行下面的赋值
    base._component = component  // 同时将 component 赋到新得到的 dom 上
  }
}

大概解释下:当 return () 后直接跟一个组件(没有根元素)时候,(应该这样处理)父组件和子组件绑定的 component 为同一个 component。所以如上代码所示只要对 return () 后不直接跟组件的情况才需要重新赋值 component。

不要用数组的 index 作 key 值

在如下 demo 中以数组的 index 作为 key 值,会发生什么呢?

import cpreact, { Component, ReactDOM, PureComponent } from '../src/index'

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      items: ['foo', 'bar'],
    }
    this.add = this.add.bind(this)
  }

  add() {
    const items = this.state.items.slice()
    items.unshift('baz')

    this.setState({
      items
    })
  }

  render() {
    return (
      <div>
        <ul>
          {this.state.items.map((r, index) => (
            <li key={index}>
              {r}
              <input />
            </li>
          ))}
        </ul>
        <button onClick={this.add}>add</button>
      </div>
    )
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)

在 foo 右侧的输入框内输入 1,

image

点击 add 按钮,

image

发现带 1 的输入框依然处在最上面!造成它的原因是因为 diff 前后具有相同的 key 的节点,cpreact/react 只会将新的节点替换掉旧的节点。

JSX 解析有所不同

class Todo extends Component {
  constructor() {
    super()
    this.state = {
      items: ['foo', 'bar'],
    }
  }

  render() {
    console.log('renderParent')
    return (
      <div>
        <List items={this.state.items} />
      </div>
    )
  }
}

class List extends Component {
  render() {
    console.log('renderItem', this.props.items)
    return (
      <ul>
        {
          this.props.items.map(item =>
            <li key={item}>{item}</li>
          )
        }
      </ul>
    )
  }
}

下面两种情况 JSX 解析有所不同:

<ul>
  {
    this.props.items.map(item => // 针对这种情况 JSX 有所不同
      <li key={item}>{item}</li>
    )
  }
</ul>

<ul>
  <li>123</li>
  <li>456</li>
</ul>

目前测试的是 this.props.items.map(item => <li>{item}</li>) 这样一层遍历的,后续如果有多层嵌套也许还会有所变化。

另外修复了个由于 '' ? 1 : 0 判断失误的 bug。

完整版测试用例

class Todo extends Component {
  constructor() {
    super()
    this.state = {
      items: ['foo', 'bar'],
    }

    this.add = this.add.bind(this)
  }

  add(value) {
    const items = this.state.items.slice()
    items.push(value)

    this.setState({
      items,
    })
  }

  render() {
    console.log('renderParent', this.state.items)
    return (
      <div>
        <List items={this.state.items} />
        <Form add={this.add} />
      </div>
    )
  }
}

class List extends Component {
  render() {
    console.log('renderItem', this.props)
    return (
      <ul>
        {
          this.props.items.map(item =>
            <li key={item}>{item}</li>
          )
        }
      </ul>
    )
  }
}

class Form extends Component {
  constructor(props) {
    super(props)
    this.state = {
      value: ''
    }

    this.handleChange = this.handleChange.bind(this)
  }

  handleChange({ target }) {
    this.setState({
      value: target.value,
    })
  }

  render() {
    console.log('renderForm', this.props)
    const { add } = this.props
    return (
      <div>
        <input
          value={this.state.value}
          onChange={this.handleChange}
        />
        <button onClick={() => add(this.state.value)}>+</button>
      </div>
    )
  }
}

事件机制优化

在 document 上使用事件委托,好处如下:

  • 添加时机不受限(在 dom 任何生命周期的里都可添加)
  • 添加事件更快
  • 内存消耗也更小
  • 减少事件的垃圾回收(如果绑定在低层级的标签上,标签内容消失时,还需要手动执行清空事件内存)

目前的计划是将 JSX 里如果有事件元素(计划支持 click、mousedown、mouseup、keydown、keyup),则在这个标签上打上一个标记(或者在内存中存份数据),冒泡到 document 的时候和这个标记进行比对。

关于 state 的一种特殊写法

为了方便书写 state,在项目中可以如下书写:

class App extends Component {
  state = {
    test: 123,
  }

  render() {
    return (
      <div>
        { this.state.test }
      </div>
    )
  }
}

react 又是如何识别出这种格式的呢,这种写法在普通的 es6 书写中是会抛错的

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.