赞
踩
组件实例的有三大核心属性:State、Props、Ref。
类组件中这三大属性都存在。
函数组件没有实例对象。但由于函数可以接收参数,因此有 Props 属性,但是依然没有 State、Ref 属性(在没有 Hooks 的情况下)。
当组件中的一些数据在某些时刻发生变化时,就需要使用 state 来跟踪状态,通过更新组件的 state 来重新渲染组件,更新对应的页面显示
state 是一个对象,包含多个 key:value
的组合。state 是私有的,只有在当前组件内部可以访问、修改 state。
class App extends React.Component {
state = {
message: 'Hello,React',
}
render() {
return <h1>{this.state.message}</h1>
}
}
state 中的数据要保持不可变性,不要直接去修改它。因为在 React.PureComponent
中,当组件更新时,只会对 state 的第一层进行浅比较,这时如果直接修改的是 state 中的某一个引用类型数据,由于引用地址没有发生变化,React.PureComponent
会认为数据没有改变而不会重新执行 render()
方法。可以通过浅拷贝这个引用类型数据来解决。
由于
React.Component
中不管数据有没有变化,只要setState()
就会重新执行render()
方法,因此没有这种问题。
class App extends React.PureComponent { state = { person: { name: 'Lee', } } handeleNameChange = () => { // 解构赋值的 person 和 state 中的 person 指向同一个对象,PureComponent 会认为数据没有变化,不会进行 render() const {person} = this.state person.name = 'Mary' this.setState({person}) // this.state.person 和 state 中的 person 指向同一个对象,PureComponent 会认为数据没有变化,不会进行 render() // this.state.person.name = 'Mary' // this.setState({person: this.state.person}) // 可以通过浅拷贝一个新对象来解决,此处的 person 和 this.state.person 不再指向同一个对象 const person = {...this.state.person} person.name = 'Mary' this.setState({person}) } render() { const {person} = this.state return <h1 onClick={this.handeleNameChange}>{person.name}</h1> } }
setState()
:setState()
可以合并对象,更改 state 中的值;并且会重新执行 render() 方法,渲染组件内容。
setState()
是从React.Component
中继承而来的。
有两种写法:
对象式的 setState 是函数式的 setState 的语法糖。
如果新状态不依赖于原状态,使用对象方式;如果新状态依赖于原状态,使用函数方式。
setState(nextState, [callback])
:对象式的 setState。 this.setState({
message:'Hello,JS',
}, () => {
console.log(this.state.message)
})
setState(updater, [callback])
:函数式的 setState。 this.setState((state, props) => ({
message: 'Hello,JS',
}), () => {
console.log(this.state.message)
})
class App extends React.Component {
state = {
message: 'Hello,React',
}
handleClick = () => {
// setState() 做了两件事:合并状态对象,更改 state 中的值;重新执行 render() 方法,渲染组件内容
this.setState({message: 'Hello,JS'})
}
render() {
return <h1 onClick={this.handleClick}>{this.state.message}</h1>
}
}
即使 state 中的值没有更改,但只要调用了 setState()
,就会重新执行 render()
方法。
class App extends React.Component {
state = {
message: 'Hello,React',
}
handleClick = () => {
// 仍然会再次执行 render() 方法
this.setState({message: 'Hello,React'})
}
render() {
return <h1 onClick={this.handleClick}>{this.state.message}</h1>
}
}
不能直接修改 state 的值,这样无法让界面发生更新。
React 中并没有实现类似于 Vue2 中的
Object.defineProperty()
或者 Vue3 中的 Proxy 的方式来监听数据的变化。
必须通过setState()
来明确地告知 React 数据已经发生了变化,React 才会再次执行render()
方法,并且根据最新的 state 来更新界面。
// 正确。setState() 做了两件事:修改 state 中的值;自动重新执行 render() 方法,渲染组件内容
this.setState({message: 'Hello,JS'})
// 错误。不能直接修改 state,因此这样不会重新渲染组件
this.state.message = 'Hello,JS'
setState()
的更新是合并:setState()
的更新是合并,而不是替换。
React 源码中是通过
Object.assign(this.state, newState)
来实现setState()
中的新对象和旧 state 对象的合并的。
class App extends React.Component {
state = {
message: 'Hello,React',
content: 'React is a JavaScript library for building user interfaces',
}
handleClick = () => {
// {} 在内存中总是会创建一个新的对象,原先的 state 也是一个对象。此处调用 setState() 时创建了一个包含 message 的新对象,但是原先 state 对象中 content 的值也并没有丢失,所以说明更新的这个动作是合并。
this.setState({message: 'Hello,JS'})
}
render() {
return <h1 onClick={this.handleClick}>{this.state.message}</h1>
}
}
setState()
的更新是异步的(批处理):setState()
的更新是异步的。React 会将多个状态更新,聚合到一次 render 中执行,以提升性能,这被称为批处理。 原因是:
setState()
都进行一次更新,意味着 render()
方法会被频繁地调用、界面会被频繁地渲染,效率很低。最好的方法就是获取到多个更新之后进行批量更新。
具体实现是:调用
setState()
后,会先将要更新的内容放到一个队列中去;等到真的要更新的时候,才会从队列中取出要更新的内容,依次按照顺序进行合并;全部合并完成之后, 调用一次render()
方法。
render()
函数,那么会导致父组件中的 state 的值已经更新了,子组件中接收到的 props 却仍然是旧的,state 和 props 不能保持同步,会在开发中产生很多问题。class Grandparent extends React.Component { state = { message:'Hello,React' } hahandleClickndle = () => { // React 会把多个 setState() 的调用合并成一个调用。点击 h1,setState() 了三次,但是只执行了一次 render() 方法 this.setState({message: 'Hello,JS'}) this.setState({message: 'Hello,JS'}) this.setState({message: 'Hello,JS'}) } render() { console.log('render') return <h1 onClick={this.handleClick}>{this.state.message}</h1> } }
// 错误
this.setState({
message:'Hello,JS',
})
console.log(this.state.message) //此时获取到的是旧的 message 值
// 正确
this.setState({
message:'Hello,JS'
}, () => {
console.log(this.state.message) //此时获取到的是新的 message 值
})
如果有特殊的情况确实需要依次更新 state,可以使用 flushSync 跳过批处理,强制同步执行。
import {flushSync} from 'react-dom' class App extends React.Component { state = { message:'Hello,React' } handleClick = () => { flushSync(() => { this.setState({message: 'Hello,JS'}) }) console.log(this.state.message) // 'Hello,JS' } render() { return <h1 onClick={this.handleClick}>{this.state.message}</h1> } }
React18 之后,
setState()
的更新一定是异步的。
React18 之前,如果setState()
不是由 React 触发的回调执行的,将是同步的(例如:setTimeout()
、原生 DOM 事件中、Promise.then()
中);其他时候则是异步的(例如:组件的生命周期中或者 React 的合成事件中)。class App extends React.Component { state = { message:'Hello,React' } componentDidMount() { // 生命周期函数是由 React 内部调用的,此处的 setState() 是由 React 触发的回调执行的 this.setState({message: 'Hello,JS'}) console.log(this.state.message) // 'Hello,React'。异步,获取到的仍然是旧的 state const dom = document.getElementById('h') dom.onclick = () => { // 此处的 setState() 在原生 DOM 事件的回调函数里,是由浏览器执行的 this.setState({message: 'Hello,JS'}) console.log(this.state.message) // 'Hello,JS'。同步,获取到的是新的 state } } render() { return <h1 id='h'>{this.state.message}</h1> ) } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
class App extends React.Component { state = { message:'Hello,React' } handleClick = () => { // handleClick 是由 React 合成事件调用的,此处的 setState() 是由 React 触发的回调执行的 this.setState({message: 'Hello,JS'}) console.log(this.state.message) // 'Hello,React'。异步,获取到的仍然是旧的 state setTimeout(() => { // 此处的 setState() 在 setTimeout() 的回调函数里,是由浏览器执行的 this.setState({message: 'Hello,JS'}) console.log(this.state.message) // 'Hello,JS'。同步,获取到的是新的 state }, 100) } render() { return <h1 onClick={this.handleClick}>{this.state.message}</h1> ) } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
React 中的 State 有三种状态管理的方式:
- 组件自己管理自己的 State。
- 使用 Context 共享状态。
- 使用 Redux 管理应用的状态。
当 React 元素是自定义组件时,它会将接收到的标签属性及子组件转换为单个对象传递到组件内部,这个对象被称之为 props。
props 是组件外部向组件内部传递的数据,是只读的。
state 和 props 的区别:
- props 由父组件传入,而 state 由组件本身管理。
- 组件不能修改 props,但可以修改 state。
<Person name='Lee' age={(function(){return 19})()} sex={<li>性别:男</li>} />
// 也可以批量传递标签属性。原生 JS 中扩展运算符是不能展开对象的。由于 React 和 Babel 的原因,扩展运算符可以展开对象,但仅仅适用于标签属性的传递,别的地方不支持。
<Person {...{
name: 'Lee',
age: (function(){return 19})(),
sex: <li>性别:男</li>
}} />
// 类组件
class Person extends React.Component{
render(){
const {name, age, sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
{sex}
</ul>
)
}
}
// 函数组件
function Person(props){
const {name, age, sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
{sex}
</ul>
)
}
props.children
:组件内部通过 props.children
可以获取到调用组件时的开始标签和结束标签之间的内容。
props.children
的值有四种类型:
import Child from './child' class Parent extends React.Component { render() { return ( <div> <Child> 这是 children 的内容 </Child> // 不写标签体,写成 children 标签属性也可以 <Child children='这是 children 的内容' /> </div> ) } } class Child extends Component { render() { return ( {/* 通过 props.children 可以获取到组件的开始标签和结束标签之间的内容 */} <div>{this.props.children}</div> ) } }
可以通过 defaultProps 属性来设置 props 中的默认值。当某个 prop 值没有被传入时,就会使用默认值。
// 给组件添加 defaultProps 属性
Person.defaultProps = {
name: 'Lee'
}
// 类组件中还可以将 defaultProps 声明为静态属性
class Person extends React.Component{
static defaultProps = {
name: 'Lee'
}
}
可以通过 propTypes 属性来检查组件接收到的数据类型是否正确。当传入的 prop 值类型不正确时,JavaScript 控制台将会显示警告。
propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps。
出于性能方面的考虑,propTypes 仅在开发模式下进行检查。
// 自 React v15.5 起,React.PropTypes 已移入 prop-types 包中,使用需要单独引入,引入之后全局就会有 PropTypes 对象,PropTypes 对象提供了一系列验证器
import PropTypes from 'prop-types'
// 给组件添加 propTypes 属性
Person.propTypes = {
name: PropTypes.string.isRequired,
speak: PropTypes.func,
}
// 类组件中还可以将 propTypes 声明为静态属性
class Person extends React.Component{
static propTypes = {
name: PropTypes.string.isRequired,
}
}
通过 Ref 可以获取到原生 DOM 和类组件实例。不要过度使用 Ref。
当 ref 用于 HTML 元素上时,获取到的是底层的 DOM 元素。
当 ref 用于类组件时,获取到的组件实例对象。
因为函数组件没有实例对象,因此无法通过 ref 来获取函数组件。
class Parent extends React.PureComponent { childRef = React.createRef() handleClick = () => { console.log(this.childRef.current) // null } render() { return ( <> <Child ref={this.childRef} /> // 错误 <button onClick={this.handleClick}>点击获取函数组件</button> </> ) } } const Child = props => { return <h2>这是 Child 组件</h2> } export default React.memo(Child)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
Ant Design 中很多组件都获取不到 ref,可以包裹或内嵌一层自己创建的元素以获取 ref。
通过 Ref 获取原生 DOM 和类组件实例有三种方式:
this.refs.ref字符串
即可获取到 React 元素。
React 不推荐使用字符串形式的 ref,它已过时并可能会在未来的版本中被移除,这种方式存在一些效率上的问题。
class App extends React.PureComponent {
handleClick = () => {
// 获取 ref
console.log(this.refs.h1Node) // <h1>标题</h1>
}
render() {
// 绑定 ref
return <h1 ref='h1Node' onClick={this.handleClick}>标题</h1>
}
}
class App extends React.PureComponent {
handleClick = () => {
// 获取 ref
console.log(this.titleRef)// <h1>标题</h1>
}
render() {
// 绑定 ref
return <h1 ref={el => this.titleRef = el} onClick={this.handleClick}>标题</h1>
}
}
React.createRef()
:使用 React.createRef()
创建一个 ref 对象后将其绑定到 React 元素上,通过 ref 对象.current
即可获取到 React 元素。推荐使用。class Parent extends React.PureComponent { // 创建一个 ref 对象 childRef = React.createRef() handleClick = () => { // 获取 ref // 获取类组件实例后即可访问其实例属性、调用实例方法等 console.log(this.childRef.current) } render() { return ( <> {/* 绑定 ref */} <Child ref={this.childRef} /> <button onClick={this.handleClick}>点击获取子组件实例</button> </> ) } } class Child extends React.PureComponent { render() { return <h2>这是 Child 组件</h2> } }
可以通过 Ref 转发来访问函数组件内部的 React 元素。
React.forwardRef()
是一个高阶函数,可以使用 React.forwardRef()
包裹匿名函数组件,匿名函数组件将会接收到转发过来的 ref 作为其第二个参数,可以将其向下传递给子组件。
class Parent extends React.PureComponent { // 1. 创建一个 ref 对象 hRef = React.createRef() handleClick = () => { // 5. 获取 ref console.log(this.hRef.current) } render() { return ( <> {/* 2. 传递 ref */} <Child ref={this.hRef} /> <button onClick={this.handleClick}>点击获取子组件中的元素</button> </> ) } } // 3. 接收 ref const Child = React.forwardRef( (props, ref) => { // 4. 绑定 ref return <h2 ref={ref}>这是 Child 组件</h2> } ) export default React.memo(Child)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。