生命周期
1、组件从创建到死亡会经历一些特定的阶段
2、React组件中包含了一系列钩子函数(生命周期回调函数),并且会在特定的时刻调用
3、我们在定义组件时,可以在一些特定的生命周期的回调函数中做一些特定的工作
挂载阶段:
当组件实例被创建并插入DOM中时,其生命周期调用顺序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
更新阶段:
当组件中的props或者state发生变化时就会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
卸载:
componentWillUnmount()
//创建组件
class App extends React.Component {
//构造器,通常适用于以下两种情况,如果不需要以下两种情况可以不用为组件实现该构造函数
//通过给this.state赋值对象来初始化内部state
//为事件处理函数绑定实例
constructor(props) {
console.log('Count---constructor');
super(props)
//初始化状态
this.state = { count: 0 }
}
//加1按钮的回调
add = () => {
//获取原状态
const { count } = this.state
//更新状态
this.setState({ count: count + 1 })
}
//卸载组件按钮的回调
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
//强制更新按钮的回调
force = () => {
this.forceUpdate()
}
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps', props, state);
// 若返回的是null则不会更新任何内容
return null
}
//组件挂载完毕的钩子
componentDidMount() {
console.log('Count---componentDidMount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(nextProps, nextState) {
console.log('Count---shouldComponentUpdate', nextProps, nextState);
// 若返回值为false则不会调用componentDidUpdate()
return true
}
//组件更新完毕的钩子
componentDidUpdate(preProps, preState, snapshotValue) {
console.log('Count---componentDidUpdate', preProps, preState, snapshotValue);
}
//在更新之前获取快照
getSnapshotBeforeUpdate() {
console.log('getSnapshotBeforeUpdate');
// 该返回值会传递给componentDidUpdate()中的第三个参数
return 'nihao'
}
//组件将要卸载的钩子
componentWillUnmount() {
console.log('Count---componentWillUnmount');
}
render() {
console.log('Count---render');
const { count } = this.state
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
复制代码
页面第一次挂载:
点击+1更新state来更新组件:
强制更新:
组件卸载:
生命周期总结:
重要的钩子:
-
render:初始化渲染或者更新渲染时调用
-
componentDidMount:开启监听,发送Ajax请求
-
componentWillUnmount:做一些收尾工作,如:清理定时器
废弃的钩子:
-
componentWillMount
-
componentWillReceiveProps
-
componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
条件渲染
在React中,可以使用JavaScript中运算符if
或者条件运算符
去创建元素来表现当前的状态,然后让React根据他们来更新UI
例如有两个组件,通过isLogin的真假来渲染不同的组件内容
function UserGreeting() {
return <h1>Welcome back!</h1>;
}
function GuestGreeting() {
return <h1>Please sign up.</h1>;
}
function App(props) {
if (props.isLogin) {
return <UserGreeting />;
} else {
return <GuestGreeting />
}
}
复制代码
与运算符&&
通过{}包裹代码,可以在JSX中写入任何表达式
例如
function App(props) {
const unreadMessage = props.unreadMessage
return (
<div>
<h1>Hello!</h1>
{unreadMessage.length > 0 &&
<h1>
You have {unreadMessage.length} unread messages.
</h1>
}
</div>
)
}
const messages = ['React', 'Re: React', 'Re:Re: React']
ReactDOM.render(<App unreadMessage={messages} />, document.getElementById("root"));
复制代码
在JavaScript中,当true&&expression
时,将会返回expression
,而false&&expression
时总会返回false
。
因此在上述例子中如果前者条件为true
,则会渲染右侧的元素,若为false
,则会忽略并跳过它
三目运算符
在React中不仅可以使用&&运算符来进行条件渲染,也可以使用三目运算符来完成
例如
function App(props) {
const isLogin = props.isLogin;
return (
<h1>
The user is <b>{isLogin ? 'currently' : 'not'}</b> login in
</h1>
)
}
复制代码
列表&key
我们可以像JavaScript中的一些遍历的方法来渲染列表组件,通过使用{}在JSX中构建一个元素集合
例如
function App() {
const numbers = [1, 2, 3, 4, 5];
return (
<h1>
{numbers.map((number) => (
<div>{number}</div>
))}
</h1>
)
}
复制代码
但是这样写会有一个警告Each child in a list should have a unique "key" prop.
,意思是当创建一个元素时,必须包括一个特殊的key
属性
即改为:
function App() {
const numbers = [1, 2, 3, 4, 5];
return (
<h1>
{numbers.map((number) => (
<div key={number}>{number}</div>
))}
</h1>
)
}
复制代码
DOM的Diffing算法
当setState()更新状态了之后,会重新创建一个新的虚拟DOM树,这个新的虚拟DOM会去跟之前旧的虚拟DOM树进行比较差异,并且更新有差异的部分再将新的数据渲染到真实的DOM上面。
key的作用
key可以帮React识别出来哪些元素改变了,比如被添加或者删除。因此应当给一个数组的每一个元素赋予一个确定的标识。
同时一个元素的key最好是这个元素在列表中拥有一个独一无二的字符串。通常可以使用数据中的id作为元素的key,当元素没有id的时候,也可以将index作为key,但是这样会引起一些问题的出现。
在进行新虚拟DOM和旧虚拟DOM的diff比较时,会有以下规则:
1、如果新虚拟DOM从旧虚拟DOM中找到了相同的key:
-
若新虚拟DOM中的内容没有变,则直接使用旧虚拟DOM中的内容和之前的真实DOM来渲染到页面上
-
若新虚拟DOM中的内容发生了变化,则使用新虚拟DOM中的内容,创建新的真实DOM,然后替换掉之前的真实DOM来渲染到页面上
2、如果新虚拟DOM中没有找到与旧虚拟DOM中相同的key,则会直接使用新虚拟DOM中的内容来创建新的真实DOM,然后渲染到页面上
为什么遍历列表,key最好不要使用index?
当使用index作为key时可能引发的问题:
(1)若对数据进行了逆序添加,逆序删除等破坏顺序的操作的话,会产生没有必要的真实DOM更新
(2)若结构中还包含了输入类的DOM,会产生错误的DOM更新,导致页面有问题
class App extends React.Component {
state = {
numbers: [1, 2, 3, 4, 5]
}
addItem = () => {
this.setState({
numbers: [this.state.numbers.length + 1, ...this.state.numbers]
})
}
render() {
const { numbers } = this.state;
return (
<>
<h3>
{numbers.map((number, index) => (
// 使用index值作为key值
<div key={index}>
{number}:
<input type="text" />
</div>
))}
</h3>
<button onClick={this.addItem}>Add Item</button>
</>
)
}
}
复制代码
若使用index作为key值时,对数据进行了逆序的添加删除,此时页面就会出现以下的错误
class App extends React.Component {
state = {
numbers: [1, 2, 3, 4, 5]
}
addItem = () => {
this.setState({
numbers: [this.state.numbers.length + 1, ...this.state.numbers]
})
}
render() {
const { numbers } = this.state;
return (
<>
<h3>
{numbers.map((number) => (
// 使用唯一的值作为key值
<div key={number}>
{number}:
<input type="text" />
</div>
))}
</h3>
<button onClick={this.addItem}>Add Item</button>
</>
)
}
}
复制代码
当使用唯一的值作为key值时,无论是正序还是逆序都不会影响到页面。