元素与组件
const div = React.createElement('div',...)
复制代码
这是一个元素(d小写)
const Div = ()=>React.createElement('div'..)
复制代码
这是一个组件(D大写)
-
什么是组件
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
组件可以理解为页面的一部分,可以和其他组件组合起来形成完成的页面。
就目前而言,一个返回React元素的函数就是组件。在Vue里,一个构造选项就可以表示一个组件。
React两种组件
一、函数组件
定义组件最简单的方式就是编写 JavaScript 函数:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
复制代码
该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。
使用方法:<Welcome name="frank"/>
二、类组件
你同时还可以使用 ES6 的 class 来定义组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
复制代码
上述两个组件在 React 里是等效的。
使用方法:<Welcome name="frank"/>
二者区别与共同点
共同点
- 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props。
- 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。
- React是单项数据流,父组件改变了属性,那么子组件视图会更新。
- 属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改
组件的属性和状态改变都会更新视图
区别
- 函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。
- 为了提高性能,尽量使用函数组件
<Welcome />
会被翻译成什么
<div />
翻译为React.createElement('div')
<Welcome />
翻译为React.createElement(Welcome)
- babel online直接翻译
React.createElement的逻辑
- 如果传入一个字符串‘div’,则会创建一个div
- 如果传入一个函数,则会调用该函数,获取其返回值
- 如果传入一个类,则在类前面加个new, 执行构造函数constructor,获取组件对象,然后调用对象的render方法,获取返回值
注意: 组件名称必须以大写字母开头。
React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div />
代表 HTML 的 div 标签,而 <Welcome />
则代表一个组件,并且需在作用域内使用 Welcome。
小试牛刀
类组件和函数组件使用 props&state
二者作用
props
和 state
都是普通的 JavaScript 对象。它们都是用来保存信息的。组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下层组件需要使用上层组件的数据或方法,上层组件就可以通过下层组件的 props
属性进行传递,因此 props
是组件对外的接口。组件除了使用上层组件传递的数据外,自身也可能需要维护管理数据,这就是组件对内的接口 state
。根据对外接口 props
和对内接口 state
,组件计算出对应界面的UI
一.props(外部数据)
props是组件对外的数据接口,只能读不能写
- 类组件读取props属性:
this.props.xxx
- 函数组件读取props参数:
props.xxx
class Son extends React.Component {
render() {
return (
<div className="Son">
我是儿子,我爸对我说「{this.props.messageForSon}」
<Grandson messageForGrandson="孙贼你好" />
</div>
);
}
}
const Grandson = props => {
return (
<div className="Grandson">
我是孙子,我爸对我说「{props.messageForGrandson}」
</div>
);
};
复制代码
二.state (内部数据)
state
是组件对内的数据接口,能读能写
- 类组件读取state:
this.state
, 写入为this.setState
。推荐写为this.setState=()=>{****}
, 因为更好理解异步的setStatesetState
之后,state不会马上改变,立马读取state会失败,推荐使用setState(函数)this.setState(this.state)
不推荐,React希望我们不要修改旧state(不可变数据),常用代码setState({n:state.n+1})
- 函数组件用
usestate
返回数组,第一项读,第二项写- 没有this,一律用参数和变量
函数组件使用 React.useState()
是赋值,它会返回一个新的数组,然后使用解构赋值。数组的第一个元素为赋值,第二项为修改值
class Son extends React.Component {
constructor() {
super();
this.state = {
n: 0
};
}
add() {
// this.state.n += 1 为什么不行
//因为React没有像Vue一样监听data一样监听state
this.setState({ n: this.state.n + 1 });
}
render() {
return (
<div className="Son">
儿子 n: {this.state.n}
<button onClick={() => this.add()}>+1</button>
<Grandson />
</div>
);
}
}
const Grandson = () => {
const [n, setN] = React.useState(0);//解构赋值
return (
<div className="Grandson">
孙子 n:{n}
<button onClick={() => setN(n + 1)}>+1</button>
</div>
);
};
复制代码
复杂的state写法
类组件写法(自动合并)
class Son extends React.Component {
constructor() {
super();
this.state = {
n: 0,
m: 0
};
}
addN() {
this.setState({ n: this.state.n + 1 });
}
addM() {
this.setState({ m: this.state.m + 1 });
}
render() {
return (
<div className="Son">
儿子 n: {this.state.n}
<button onClick={() => this.addN()}>n+1</button>
m: {this.state.m}
<button onClick={() => this.addM()}>m+1</button>
<Grandson />
</div>
);
}
}
复制代码
-
小结
类组件的
setState
修改state
的部分属性,另外未修改的属性会自动沿用之前的属性,二者会自动合并。值得注意的是:第一层的属性会自动合并。如果n和m里面还有属性,对象中的属性变更,第二层属性并不会自动合并,例如
this.state = {
n: 0,
m: 0,
user: {
name: "frank",
age: 18
}
};
复制代码
如果修改 user.name
,n和m会自动合并,而age不会合并会变成空
解决二层属性不会自动合并,可以使用 Object.assign 或者…操作符,例如
// Ojbect.assign会新建空对象,把之前的数据复制到空对象里
changeUser() {
const user = Object.assign({}, this.state.user);
user.name = "jack";
this.setState({
user: user
});
}
//...
changeUser() {
this.setState({
// m 和 n 不会被置空
user: {
...this.state.user, // 复制之前的所有属性
name: "jack"
// age 被置空
}
});
复制代码
函数组件的写法(不会自动合并)
const Grandson = () => {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
return (
<div className="Grandson">
孙子 n:{n}
<button onClick={() => setN(n + 1)}>n+1</button>
m:{m}
<button onClick={() => setM(m + 1)}>m+1</button>
</div>
);
};
复制代码
函数组件则完全不会自动合并,需要使用操作符 ...
进行合并,例如
const Grandson = () => {
const [state, setState] = React.useState({
n: 0,
m: 0
});
return (
<div className="Grandson">
孙子 n:{state.n}
<button onClick={() => setState({...state, n: state.n + 1 })}>n+1</button>
m:{state.m}
<button onClick={() => setState({...state, m:state.m + 1})</button>>
</div>
);
};
复制代码
三.state的注意事项(类组件)
不能直接修改State
因为组件并不会重新重发 render
。在下面的例子中,其实n已经改变了,但是UI不会自动更新,只有调用 setState
才会触发UI更新。React并不像Vue一样是数据响应式,监听到数据变化就触发视图更新
// 错误的写法
this.state = n +1
// 正确的写法
this.setState = ({n: state.n +1})
复制代码
setState是异步更新UI
调用setState,组件的state并不会立即改变,立马读取state会失败。因为setState是异步执行的,而且要把修改的状态放入一个队列中,React会优化真正的执行时机,并且React会出于性能原因,可能会将多次setState的状态修改合并成一次状态修改。需要注意的是,同样不能依赖当前的props计算下个state,因为props的更新也是异步的。推荐使用的方式是setState(fn函数)
State 的更新是浅合并
当调用setState修改组件状态时,只需要传入发生改变的状态变量,而不是组件完整的state,因为组件state的更新是一个浅合并Shallow Merge的过程。
// 例如,一个组件的state为
this.state = {
title : 'React',
content : 'React is an wonderful JS library!'
}
// 当只需要修改状态title时,只需要将修改后的title传给setState
this.setState({title: 'Reactjs'});
// React会合并新的title到原来的组件state中,同时保留原有的状态content,合并后的state为
{
title : 'Reactjs',
content : 'React is an wonderful JS library!'
}
复制代码
不推荐使用this.setState(this.state)
因为state为不可变数据immutable data,React不希望我们修改旧的state。所以推荐保留旧的数据的同时新建对象来操作state。例如 setState({n: state.n+1})
四.事件绑定
类组件的事件绑定
写法一:最安全的写法
<button onClick={() => this.addN()}>n+1</button>
//箭头函数无法改变this
// 另外一种写法,命名函数,再传入绑定事件
this._addN =()=>this.addN()
<button onClick={this._addN}>n+1</button>
复制代码
写法二: addN里面的this会指向全局变量window,不推荐
<button onClick={this.addN}>n+1</button>
复制代码
写法三:使用bind绑定当前this的新函数,很麻烦
<button onClick={this.addN.bind(this)}>n+1</button>
复制代码
写法四:最终写法,this指向正确
constructor(){
this.addN = ()=> this.setState({n: this.state.n + 1})
}
//上面用的太多了,React就发明了下面的新语法,可以把addN直接放在外面
addN = () => this.setState({n: this.state.n + 1});
复制代码
也就是说,最终写法为
class Son extends React.Component{
addN = () => this.setState({n: this.state.n + 1});
render(){
return <button onClick={this.addN}>n+1</button>
}
}
复制代码
写法五:错误写法,this会指向window
class Son extends React.Component{
addN(){
this.setState({n: this.state.n + 1})
}
// 二者写法完全相等
addN: functioin(){
this.setState({n: this.state.n + 1})
}
}
复制代码
写法四 VS 写法五
- 写法四的addN函数是对象本身的属性,意味着每个Son组件都可有自己的addN函数,例如,两个Son,每个都会有自己的addN函数。因为this指向Son组件
- 写法五的addN函数是对象的共有属性,也就是原型上的属性,即prototype里面。意味着所有的Son组件共用一个addN函数。因为this指向全局变量window
- 所有函数的this都是参数,由调用决定,所以可变
- 箭头函数的this不变,因为箭头函数不接受this
引出this面试题
答案详见链接
面试题一
var a = {
name:" 里面的name",
sayName:function(){
console.log("this.name = " + this.name);
}
};
var name = "外面的name";
function sayName(){
var sss = a.sayName;
sss(); //this.name = ?
a.sayName(); //this.name = ?
(a.sayName)(); //this.name = ?
(b = a.sayName)();//this.name = ?
}
sayName()
复制代码
面试题二
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();//第一个参数,其实就是fn()
}
};
obj.method(fn, 1)
复制代码
五.Props
VS state
props
是传递给组件的(类似于函数的形参),而state
是在组件内被组件自己管理的(类似于在一个函数内声明的变量)props
是组件的只读属性,组件内部不能直接修改props
,要想修改props
,只能在该组件的上层组件中修改state
是在组件中创建的,一般在constructor
中初始化state
state
是可变的,是组件内部维护的一组用于反映组件UI变化的状态集合state
是多变的、可以修改,每次setState
都异步更新的
Vue VS React(规则与自由)
相同点
- 都是对视图的封装,React是用类和函数表示一个组件,而Vue是通过构造选项构造一个组件
- 都提供了
createElement
的XML简写,React提供的是JSX语法,而Vue提供的是模板写法,语法很多
不同点
- React是把HTML放在JS里写(HTML in JS),Vue是把JS放在HTML里写(JS in HTML)
总结:
- Vue: 能把你做的,都帮你做了
- React: 能不做的,我都不做
- vue:把JS放在HTML里写,JS in HTML
- React: 把HTML放在JS里写,HTML in JS