实现一个简化版的React——从组件的复用讲起
2019-02-18 本文已影响29人
HolidayPeng
一、如何实现组件的复用?
当我们需要一个按钮,点击之后提示当前时间,你可能会这么写:
<body>
<div class="container">
<button class="btn"> What time is it? </button>
</div>
<script>
document.querySelector('.btn').onclick = function() {
alert(new Date());
}
</script>
</body>
当有很多地方都会用到这段代码时,你可能会想到要把这块功能封装成一个组件,放在不同的父组件里。那么问题来了,怎么把子组件中的HTML部分和JavaScript部分分别放在父组件的HTML和JavaScript中呢?你可能会这么写:
const DomStr = '<button class="btn"> What time is it? </button>';
const container = document.createElement('div');
container.innerHTML = DomStr;
container.addEventListener('click', function(){
alert(new Date())
}, false);
我们进一步把这段代码封装成一个方法,方便在父组件中导入:
export function alertTimeButton(parentNode) {
const domStr = '<button class="btn"> What time is it? </button>';
const container = document.createElement('div');
container.innerHTML = domStr;
container.addEventListener('click', function(){
alert(new Date())
}, false);
parentNode.appendChild(container);
}
这段代码做了四件事情:
- 把组件的HTML部分用字符串的形式保存在一个常量里;
- 创建一个DOM容器,把组件放进去;
- 绑定点击事件;
- 把装有组件的容器加到父组件的节点上。
如果我们需要编写其他功能的组件,第1、2、4条是不是可以提出来作为公共的部分呢?我们是不是可以写一个Component类,然后让功能组件继承这个类呢?
class Component {
render (domStr, container) {
const container = document.createElement('div');
container.innerHTML = domStr;
parentNode.appendChild(container);
}
class AlertTimeButton extends Component {
constructor() {
super();
}
alertTime() {
alert(Date.now());
}
this.render(`<button id="btn" onclick="${this.alertTime.bind(this)}"> What time is it? </button>`, document.querySelector('.root'));
}
这样每次需要复用这个组件的时候就把它实例化,需要几个就实例化几个。
以上是针对简单组件的封装,当遇到父子组件嵌套和数据传递的时候,我们又该怎么做呢?
二、如何实现组件间数据的共享?
我们把上面的需求改一下:
- 在父组件中显示当前时间;
- 点击父组件,更新当前时间;
- 点击父组件下的子组件按钮,弹窗提示父组件中显示的时间。
于是有下面的结构:
class Btn extends Component {
constructor(props) {
super(props);
}
alertTime() {
alert(this.props.now);
}
render() {
return `<button class="btn"> What time is it? </button>`;
}
componentDidMount() {
document.querySelector('.btn').addEventListener('click', this.alertTime.bind(this), false)
}
}
class Timer extends Component {
constructor() {
super();
this.state = {
now: Date.now()
}
}
updateTime() {
this.setState({
now: Date.now()
});
}
componentDidMount() {
document.querySelector('.area').addEventListener('click', this.updateTime.bind(this), false)
}
render() {
return (
`<div class="area"> It's ${this.state.now}
<Btn now="${this.state.now}"/>
</div>`;
)
}
}
写到这里我们会遇到几个问题:
- 怎么把字符串模板中的组件和参数解析出来?
- 如何在state发生改变的时候重新渲染组件?
- 生命周期钩子,如componentDidMount在什么时候添加?
对于第一个问题,JSX已经帮我们做了:
JSX
对于第二个问题,最简单粗暴的方式,就是在setState方法中重新渲染一遍组件, 同时在渲染结束后添加生命周期钩子:
class Component {
constructor(props) {
this.props = props;
}
renderDom(parentNode) {
const container = document.createElement('div');
container.innerHTML = this.render();
parentNode.appendChild(container)
if (this.componentDidMount) this.componentDidMount()
}
setState(obj) {
Object.keys(obj).forEach(key => {
if (this.state[key] !== obj[key]) {
this.state[key] = obj[key];
this.renderDom(parentNode);
}
})
}
}
这样当父组件连同子组件一起被重新渲染后,传递给子组件的props自然也被更新了。