一、JSX
1. 什么是JSX
- Facebook起草的JS扩展语法
- 本质是一个JS对象,会被babel编译,最终会被转换为React.createElement
- 每个JSX表达式,有且仅有一个根节点
- React.Fragment
const h1 = ( <h1>Hello World <span>span元素</span></h1> ) // 编译成: React.createElement("h1", {}, "Hello World", React.createElement("span", {}, "span元素" )) // 如果有的时候最外层必须有两个节点,又不想套一个真实的元素,就可以: <> <h1>Hello World <span>span元素</span></h1> <p>一个p元素</p> </> // <></>是一个语法糖,就相当于写为: <React.Fragment> <h1>Hello World <span>span元素</span></h1> <p>一个p元素</p> </React.Fragment>
- React.Fragment
- 每个JSX元素必须要有结束(XML规范)。自结束也可以
2. 在JSX中嵌入表达式
表达式要写在大括号中
- 可以将表达式作为内容的一部分
- null、undefined、false不会显示
- 普通对象,不可以作为子元素。会报错
- 可以放置React元素对象
- 如果放置数组,会遍历数组,将每一项作为子元素
- 可以将表达式作为元素属性
- 在JSX中属性
class要写为className
- 在JSX中属性
const url = ".......";
const div = (
<div>
<img src={url} alt=""/>
</div>
)
// style写这样写:
<img style={{
// 外面的{}说明里面写的是表达式
// 里面的{}是一个对象
marginLeft: "50px",
}}/>
-
属性使用小驼峰命名法
-
防止注入攻击
- 自动编码
const content = "<h1>abcabc</h1><p>123123</p>" const div = ( <div> {content} </div> ) // 页面上显示的是:<h1>abcabc</h1><p>123123</p> // 相当于调用的是div.innerText方法- dangerouslySetInnerHTML
// 如果有的时候我们知道内容是安全的,可以作为元素放进去 const content = "<h1>abcabc</h1><p>123123</p>" const div = ( <div dangerouslySetInnerHTML={{ __html: content }}> </div> ) // 如果你这么写还没被恶心到的话,说明你真的有这种需求 -
在JSX中使用注释,用的是js注释
3. react元素的不可变性
- 虽然JSX元素是一个对象,但是该对象中的所有属性不可更改
- 如果确实需要更改元素的属性,需要重新创建JSX元素
实际上是调用Object.freeze()方法把对象冻结了
二、组件和组件属性
组件:可以理解为包含内容、样式和功能的UI单元
1. 创建一个组件
特别注意:组件的名称首字母必须大写,否则会被解析为一个普通元素
- 函数组件
返回一个React元素
- 类组件
必须继承React.Component
必须提供render函数,该方法必须返回react元素,用于渲染组件
2. 组件的属性
- 对于函数组件,属性会作为一个对象的属性,传递给函数的参数
- 对于类组件,属性会作为一个对象的属性,传递给构造函数的参数
组件的属性,应该使用小驼峰命名法
组件无法改变自身的属性。
之前学习的React元素(JSX),本质上,就是一个组件(内置组件)。
可以将一个组件和一个用JSX语法写的元素打印输出看一下,是一样的。所以,JSX元素的属性和组件属性都不可更改
React中的哲学:数据属于谁,谁才有权力改动
所以,React中的数据,是自顶而下流动的
三、组件状态
组件状态:组件可以自行维护的数据
组件状态仅在类组件中有效
状态(state),本质上是类组件的一个属性,是一个对象
状态初始化
状态是必须要初始化的
- 在构造函数中初始化
constructor(props) {
super(props);
this.state = {
left: this.props.number
}
}
- 在类中直接写
state = {
left: this.props.number
}
状态的变化
组件中的状态不能直接改变:因为React无法监控到状态发生了变化
必须使用this.setState({})改变状态
一旦调用了this.setState,会导致当前组件重新渲染,传入对象,与之前的状态进行混合
组件中的数据
- props:该数据是由组件的使用者传递的数据,所有权不属于组件自身,因此组件无法改变该数据
- state:该数据是由组件自身创建的,所有权属于组件自身,因此组件有权改变该数据
四、事件
在React中,组件的事件,本质上就是一个属性。只不过这个属性传递的是一个函数
按照之前React对组件的约定,由于事件本质上是一个属性,因此也需要使用小驼峰命名法
如果没有特殊处理,在事件处理函数中,this指向undefined
因为当父组件给一个子组件传入事件函数时,会把这个事件放到子组件的props当中。我们调用的时候肯定是通过props.事件调用的,所以这里函数里的this应该指向props。显然这不是我们想要的,所以,如果没经过特殊处理,react会将组件props中的事件函数中的this指向为undefined。
所以,这里我们可以使用bind和箭头函数
- 使用bind函数,绑定this
这样就算在子组件中通过props调用函数,但是因为函数通过bind绑定了this,所以this依然指向父组件对象
// 第一种写法:
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this);
}
// 第二种写法:传递的时候再绑定
<ChildComp onClick={this.handleClick.bind(this)}/>
- 使用箭头函数
handleClick = () => {
console.log(this);
}
五、深入认识setState
setState,它对状态的改变,可能是异步的
如果改变状态的代码处于某个HTML元素的事件中,则其是异步的,否则是同步
如果遇到某个事件中,需要同步调用多次,需要使用函数的方式得到最新状态
最佳实践:
- 把所有的setState当作是异步的
- 永远不要信任setState调用之后的状态
- 如果要使用改变之后的状态,需要使用回调函数(setState的第二个参数)
- 如果新的状态要根据之前的状态进行运算,使用函数的方式改变状态(setState的第一个函数)
React会对异步的setState进行优化,将多次setState进行合并(将多次状态改变完成后,再统一对state进行改变,然后触发render)
this.setState({
}, () => {
// 回调函数,是状态更新完,并且重新渲染后才执行
})
handleClick = () => {
this.setState({
n: this.state.n + 1
})
this.setState({
n: this.state.n + 1
})
this.setState({
n: this.state.n + 1
})
}
// 这样的话,每次点击按钮执行函数,n都是+1,而不是+3.因为setState这里是异步的,
// 每次读取的时候都是原来的值
// 如果真的遇到这种情况怎么办呢?
// setState的第一个参数也可以写成函数,就可以解决上述问题
handleClick = () => {
this.setState(cur => {
// 参数cur表示当前的状态
// 该函数的返回结果,会混合(覆盖)掉之前的状态
// 该函数也是异步执行的
// 但是,cur这个参数是可信任的
return {
n: cur + 1
}
})
this.setState(cur => {
return {
n: cur + 1
}
})
this.setState(cur => {
return {
n: cur + 1
}
})
}
六、生命周期
生命周期:组件从诞生到销毁会经历一系列的过程,该过程就叫做生命周期。React在组件的生命周期中提供了一系列的钩子函数(类似于事件),可以让开发者在函数中注入代码,这些代码会在适当的时候运行。
生命周期仅存在于类组件中,函数组件每次调用都是重新运行函数,旧的组件即刻被销毁
1. 旧版生命周期
React版本 < 16.0.0
- constructor(构造函数)
- 同一个组件对象只会创建一次(所以构造函数指挥运行一次。除非组件被销毁)
- 不能在第一次挂载到页面之前,调用setState,为了避免问题,构造函数中严禁使用setState
- componentWillMount(组件即将被挂载到页面)
- 正常情况下,和构造函数一样,它只会运行一次
- 可以使用setState,但是为了避免bug,不允许使用,因为在某些特殊情况下,该函数可能被调用多次
- render(渲染虚拟DOM,成为真实DOM)
- 返回一个虚拟DOM,会被挂载到虚拟DOM树中,最终渲染到页面的真实DOM中
- render可能不只运行一次,只要需要重新渲染,就会重新运行
- render函数里严禁使用setState,因为可能会导致无限递归渲染
- componentDidMount(虚拟DOM成为真实DOM后)
- 只会执行一次
- 可以使用setState
- 通常情况下,会将网络请求、启动计时器等一开始需要的操作,书写到该函数中
- 组件进入活跃状态(等待需要重新渲染的时候(属性或状态发生变化))
- componentWillReceiveProps
- 即将接收新的属性值
- 参数为新的属性对象
- 该函数可能会导致一些bug,所以不推荐使用
- shouldComponentUpdate
- 指示React是否要重新渲染该组件,通过返回true和false来指定
- 默认情况下,会直接返回true
- componentWillUpdate
- 组件即将被重新渲染
- componentDidUpdate
- 往往在该函数中使用dom操作,改变元素
- componentWillUnmount
- 通常在该函数中销毁一些组件依赖的资源,比如计时器
2. 新版生命周期
React >= 16.0.0
React官方认为,某个数据的来源必须是单一的
- getDerivedStateFromProps
- 通过参数可以获取新的属性和状态
- 该函数是静态的
- 该函数的返回值会覆盖掉组件状态
- 该函数几乎是没有什么用
- getSnapshotBeforeUpdate
- 真实的DOM构建完成,但还未实际渲染到页面中。
- 在该函数中,通常用于实现一些附加的dom操作
- 该函数的返回值,会作为componentDidUpdate的第三个参数
七、传递元素内容
内置组件:div、h1、p等
<div>
asdfafasfafasdfasdf
</div>
如果给自定义组件传递元素内容,则React会将元素内容作为children属性传递过去。
八、表单
先了解一下 受控组件和非受控组件
受控组件:组件的使用者,有能力完全控制该组件的行为和内容。通常情况下,受控组件往往没有自身的状态,其内容完全收到属性的控制。
非受控组件:组件的使用者,没有能力控制该组件的行为和内容,组件的行为和内容完全自行控制。
表单组件,默认情况下是非受控组件,一旦设置了表单组件的value属性,则其变为受控组件(单选和多选框需要设置checked)