思路
一开始完全没有任何参照的时候,我自己想着“要如何实现react的组件”,着实有些摸不着头脑。先从比较简单的函数式组件开始: 尝试使用我们之前完成的render来渲染函数式组件:
function Dinner(props) {
return <div>
<h1>
welcome to my restaurant
</h1>
<p>
dinner has {props.menu}
</p>
</div>
}
const el = <Dinner menu="chicken, fish and fruits" />
React.render(el, document.getElementById("root"));
在npm run start之后,可以看见控制台的报错和打印信息,目前我们实现的render并不能解析组件:
函数组件
根据控制台信息,我们可以发现当传入函数组件时,vDom的tag从字符串变成了一个函数,这个函数就是我们自定义的函数组件,而attrs就是我们要传入的props。 所以将createElement从仅传回tag、attrs和children改成可以接受一个函数类型的tag,并将attrs作为参数运行。
function createElement(tag, attrs = {}, ...children) {
if (typeof tag === 'function') {
return tag(attrs);
}
return {
tag,
attrs,
children // children可能不止一个,所以使用了...运算符
};
}
然后npm run start后,打开页面,发现我们自定义的组件出现了
类组件
类组件通常使用Class MyComponent extends React.Component来定义,然后就可以使用react的setState,生命周期等内容。
class Page extends React.MyComponent {
constructor(props) {
super(props);
this.state = {
temperature: 28,
};
}
render() {
const { temperature } = this.state;
return (
<div>
今天的温度是{temperature}度
<br />
<button onClick={() => {
this.setState({
temperature: this.state.temperature + 1,
});
}}>增加一度!</button>
</div>
)
}
}
所以我们应该像React.Component一样,定义我们基类,让继承了这个基类的组件成为react的类组件。
// 基类
class MyComponent {
constructor(props) {
this.isReactComponent = true;
this.state = {};
this.props = props;
}
setState(changedState) {
const {...oldState} = this.state;
Object.assign(this.state, changedState);
// setState后触发重新渲染
this.render(this, this.container);
}
}
然后createElement时,虽然类组件本质也是一个function,但它需要通过构造函数的方式去初始化,并且调用它自己的render,如果直接使用,会打印如下信息:
因为我们在mountComponent中,需要判断vDom是否为类组件,类组件需要返回自己的render()中的内容作为渲染的虚拟dom。MyComponent中的isReactComponent就是帮助我们判断。
if (vDom.isReactComponent) {
const _tempDom = vDom;
_tempDom._container = container;
vDom = _tempDom.render();
}
然后运行上方的,发现渲染成功
但是问题是:点击“增加一度”,并没有触发state变化后的再次渲染,再次触发渲染需要上一次渲染时的container属性,所以我们再将container带入
class MyComponent {
constructor(props) {
this.isReactComponent = true;
this.state = {};
this.props = props;
}
setState(changedState) {
Object.assign(this.state, changedState);
// setState后触发重新渲染
render(this, this._container);
}
}
function render(vDom, container) {
// 清空上一次挂载的内容,准备挂载新的内容
console.log("render start");
container.innerHTML = "";
container && container.appendChild(mountComponent(vDom, container));
console.log("render end");
}
最后尝试,发现成功!
总结
React组件分为函数组件和类组件:
函数组件需要在初始化时,将props作为参数直接执行此函数,return的内容就是要渲染的虚拟dom。
类组件有自己的render()内容,所以初始化组件时需要区分,然后调用类组件自己的构造函数,并真实render()的是类组件render()后的结果!
之后会开始讨论state和props改变,及相关生命周期。
PS:贴出来的代码会随着不断更新而变更,可能会有部分有问题的情况,等整个系列结束后会贴完整代码^ ^,有疑问的地方也参考了网络上的一些结果,但是也希望自己通过这个过程理解react~