Git Product home page Git Product logo

cpreact's People

Contributors

muyunyun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

luorijin hufans

cpreact's Issues

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

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

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

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>
    )
  }
}

关于 svg 渲染和显示

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

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

image

关于 state 的一种特殊写法

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

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

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

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

踩坑日志: 关于 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 的值
    )
  }
}

一些有用的测试用例

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} />
      )} />
    )
  }
}

事件机制优化

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

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

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

不要用数组的 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 只会将新的节点替换掉旧的节点。

踩坑日志: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。

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.