类组件和函数组件
Element V.S. Component
元素与组件
// React 元素(d小写)
const div = React.createElement('div',...)
// React 组件(D大写)
const Div = () => React.createElement('div'..)
什么是组件
能跟其他物件组合起来的物件,就是组件
就目前而言,一个返回 React 元素的函数就是组件
在 Vue 里,一个构造选项就可以表示一个组件
React 两种组件
函数组件
function Welcome(props){
return <h1>Hello,{props.name}</h1>
}
// 使用方法: <Welcome name="river"/>
类组件
class Welcome extends React.Component {
render() {
return <h1>Hello,{this.props.name}</h1>
}
}
// 使用方法: <Welcome name="river"/>
<Welcome />
会被翻译成什么
<div />会被翻译为 React.createElement('div')
<Welcome />会被翻译为 React.createElement(Welcome)
React.createElement 的逻辑
- 传一个字符串
'div',会创建一个div - 传一个函数,会调用该函数,获取其返回值
- 传一个类,会在类前面加个new(这会导致执行
constructor),获取一个组件对象,然后调用对象的render方法,获取其返回值
使用 props 和 state
小试牛刀
代码示例
添加 props (外部数据)
类组件直接读取属性 this.props.xxx
函数组件直接读取参数 props.xxx
添加 state (内部数据)
类组件用 this.state读,this.setState写
函数组件用 useState返回数组,第一项读,第二项写
注意:setState会等一会再改变n,而setN不会改变n
注意事项
类组件注意事项
this.state.n += 1 无效?
其实n已经改变了,只不过 UI不会自动更新而已
调用setState才会触发UI更新 (异步更新)
因为React没有像Vue监听data一样监听state
setState 会异步更新UI
setState之后,state不会马上改变,立马读state会失败
更推荐的方式是setState(函数)
this.setState(this.state)不推荐?
React希望我们不要修改旧的state(不可变数据)
常用代码:setState({ n: state.n+1 })
总结
这是一种理念(函数式)
函数组件注意事项
跟类组件类似的地方
也要通过setX(新值)来更新UI
跟类组件不同的地方
没有this,一律用参数和变量
两种编程模型
Vue 的编程模型
一个对象,对应一个虚拟DOM。当对象的属性改变时,把属性相关的DOM节点全部更新。
注:Vue为了其他考量,也引入了虚拟DOM和DOM diff,此图只是简化。
React 的编程模型
一个对象,对应一个虚拟DOM。另一个对象,也对应另一个虚拟DOM。
对比两个虚拟DOM,找不同(DOM diff),最后局部更新DOM。
复杂 state
如果state里不止有n怎么办
注意:
class 组件里的setState,如果对其中一部分进行修改,那么其他部分会自动沿用上一次的值,不会被undefined覆盖。
函数组件有另一种不推荐写法,m被空置。(需要用...操作符)
总结
类组件的setState会自动合并第一层属性,但是并不会合并第二次属性,那怎么办?
函数组件的setX则完全不会帮助合并,如果合并需要自己用...操作符合并
绑定事件
onClick、onKeyPress...
类组件的事件绑定
<button onClick={() => this.addN()}>n+1</button>
// 传一个函数给 onClick 即可
<button onClick={this.addN}>n+1</button>
// 有问题,这样会使得 this.addN 里的 this 变成 window
<button onClick={this.addN.bind(this)}>n+1</button>
// 这样写可以,因为它返回了一个绑定了当前this的新函数
<button onClick={this._addN}>n+1</button>
// 在constructor中声明 this._addN = ()=>this.addN() ,即给箭头函数取一个名字
上面的第四种写法继续优化,直接写成:
constructor(){
this.addN = () => this.setState({n: this.state.n+1})
}
render(){
return <button onClick={this.addN}>n+1</button>
}
最终写法
class Son extends React.Component{
addN = () => this.setState({n: this.state.n + 1});
render(){
return <button onClick={this.addN}>n+1</button>
}
}
// 使用新语法,addN直接写在class里,且不需要写this
// 这里的 addN 等价于上面的 constructor 中的 addN
那么问题来了
class Son extends React.Component{
addN = () => this.setState({n: this.state.n + 1})
addN(){
this.setState({n: this.state.n + 1})
}
}
// 这两个 addN 有什么区别?
解答
class Son extends React.Component{
addN = () => this.setState({n: this.state.n + 1});
constructor(){
this.addN = () => this.setState({n: this.state.n + 1});
}
}
// 两个 addN 写法完全等价
addN(){
this.setState({n: this.state.n + 1})
}
add: function(){
this.setState({n: this.state.n + 1})
}
// 两个 addN 写法完全等价
两组 addN 的区别
第一组 addN 在对象上,第二组 addN在原型上
第二组的this可变成window,第一组不会
两个例子
例1
箭头函数定义会放在对象自己身上
例2
上面例子可以得出一个结论,所有定义到class的方法都会被放到对象的原型上
区别
第一组函数是对象本身的属性,这意味着每个Son组件都有自己的addN,如果有两个Son,就有两个addN
第二组函数是对象的共用属性(也就是原型上的属性),这意味着所有Son组件共用一个addN
为什么 this会变 / 不会变
所有函数的this都是参数,由调用决定,所以可变
唯独箭头函数的this不变,因为箭头函数不接受this
结论
优先使用函数组件,完全不用this
如果使用类组件,那么用第一组的方法更好