muyunyun / cpreact Goto Github PK
View Code? Open in Web Editor NEWBuild React from Scratch :tada:
License: MIT License
Build React from Scratch :tada:
License: MIT License
0009
PureComponent 下,验证函数以属性方式从父组件到子组件时候是否完成刷新
答案是'是'。通过分析,这个属于基本类型/引用类型的知识点,无需实验,靠逻辑就能想通。
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>
)
}
}
为了方便书写 state,在项目中可以如下书写:
class App extends Component {
state = {
test: 123,
}
render() {
return (
<div>
{ this.state.test }
</div>
)
}
}
react 又是如何识别出这种格式的呢,这种写法在普通的 es6 书写中是会抛错的
先来看下原生 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 指向对我们使用者来说就是黑盒。
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 的值
)
}
}
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')
)
// 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')
)
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')
)
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>
)
}
}
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>
)
}
}
// 测试用例:验证 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
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 上使用事件委托,好处如下:
目前的计划是将 JSX 里如果有事件元素(计划支持 click、mousedown、mouseup、keydown、keyup),则在这个标签上打上一个标记(或者在内存中存份数据),冒泡到 document 的时候和这个标记进行比对。
在如下 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,
点击 add 按钮,
发现带 1 的输入框依然处在最上面!造成它的原因是因为 diff 前后具有相同的 key 的节点,cpreact/react 只会将新的节点替换掉旧的节点。
案例如下:
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 里的如下代码:
Line 82 in aeda875
解决如下:
/**
* 自定义组件渲染逻辑
* @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。
在该 issue 中,将开启此项目的测试之旅。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.