React组件实例三大核心属性State props Refs详解
作者:花铛
组件组件实例的三大核心属性-State
状态 state 是组件实例对象最重要的属性之一,它的值是一个对象,可以包含多个 key-value 的组合。
当组件中的一些数据在某些时刻发生变化时,就需要使用 state 来跟踪状态。state 是私有的,并且完全受控于当前组件,除了拥有并设置了它的组件,其他组件都无法访问。
组件被称为状态机,通过更新组件的 state 来重新渲染组件,更新对应的页面显示。
this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实可以向类组件中随意添加不参与数据流的额外字段(比如 this.timerID
)。
state 和 props 之间最重要的区别是:
- props 由父组件传入,而 state 由组件本身管理。
- 组件不能修改 props,但可以修改 state。
class Weather extends React.Component{ constructor(props) { super(props) // 初始化 state this.state = { isHot: false, } } // state可以简写成如下形式 // 原因是:类中可以直接写赋值语句,实际上就是直接给实例对象上添加属性 state = { isHot: false, } componentDidMount() { // 更改 state this.setState({ isHot: !this.state.isHot, }) } ... }
State 不可以直接修改
初始化 state 之后,在其他地方不可以直接修改 state,而是应该使用 React 内置的一个 API:setState() 来修改。
constructor() 只初始化的时候调用一次。
render() 会调用 1+n 次,1是初始化,n 是状态更新的次数(也就是说,每次 setState() 之后, React 都会调用一次 render())。
// Wrong,此代码不会重新渲染组件 this.state.comment = 'Hello'; // Correct this.setState({comment: 'Hello'});
setState() 有两种写法:
setState(nextState, [callback]):对象式的 setState。
参数:
- nextState:将要设置的新状态,该状态会和当前的 state 合并。
- callback:可选参数,回调函数。该函数会在状态更新完毕,且界面也更新后(render() 后)调用。
this.setState({ count: this.state.count +1, }, () => { console.log(this.state.count) })
setState(updater, [callback]):函数式的 setState。
参数:
- updater:是一个函数,可以接收到 state 和 props 作为参数,返回值为将要设置的新状态。
- callback:可选参数,回调函数。该函数会在状态更新完毕,且界面也更新后(render() 后)调用。
this.setState((state, props) => ({ count: state.count +1, }), () => { console.log(this.state.count) })
对象式的 setState 是函数式的 setState 的简写方式(语法糖)。这两种写法的使用原则:
如果新状态不依赖于原状态,使用对象方式;如果新状态依赖于原状态,使用函数方式。如果需要在 setState() 执行后获取最新的状态数据,要在第二个参数 callback 函数中读取。
State 的更新是合并
setState() 的更新是合并,不是替换。
constructor(props) { super(props); // state 包含几个独立的变量 this.state = { isHot: false, wind: '微风', } } componentDidMount() { // 此处调用 setState() 更新了 isHot 的值,但是 wind 的值也并没有丢失,所以说明更新的这个动作是合并 this.setState({ isHot: !this.state.isHot, }) }
State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以不要依赖他们的值来更新下一个状态。要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。
// Wrong this.setState({ count: this.state.count + 1, }) console.log(this.state.counter) //此时直接读取获取到的仍然是旧的 count 值 // Correct this.setState({ count: this.state.count + 1, }, () => { console.log(this.state.counter) //此时读取获取到的是新的 count 值 })
组件实例对象的三大核心属性-Props
当 React 元素为用户的自定义组件时,它会将所接收的标签属性及子组件转换为单个对象传递给组件,这个对象被称之为 “props”。
props 是 React 组件的输入。它们是组件外部向组件内部传递变化的数据。
props 是只读的,组件无论是使用函数组件还是类组件,都决不能修改自身的 props。
// 类组件 class Person extends React.Component{ render(){ const {name, age, sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age+1}</li> </ul> ) } } // 函数式组件 function Person(props){ const {name, age, sex} = props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age+1}</li> </ul> ) } ReactDOM.render(<Person name="jerry" age={19} sex="男"/>, document.getElementById('test')) // 批量传递 props 的简写方法(或者叫批量传递标签属性): // 原生 JS 中扩展运算符是不能展开对象的。 // 由于 React 和 Babel 的原因,扩展运算符可以展开对象,但仅仅适用于标签属性的传递,别的地方不支持。 ReactDOM.render(<Person {...{ name: 'jerry', age: 19, sex: '男' }} />, document.getElementById('test'))
props.children
每个组件都可以获取到 props.children,它包含组件的开始标签和结束标签之间的内容。
<Welcome>Hello world!</Welcome> // 不写标签体,写成 children 标签属性也可以 <Welcome children='Hello world!'></Welcome> // 在 Welcome 组件中获取 props.children,就可以得到字符串 Hello world! function Welcome(props) { return <p>{props.children}</p>; }
使用defaultProps设置默认的prop值
可以通过配置特定的 defaultProps 属性来定义 props 的默认值。
// 给组件加上 defaultProps 的属性 Person.defaultProps = { title: '我是详情' } // 简写:简写的这种方式只适用于类组件,因为函数式组件中是没有 static 的 class Person extends React.Component{ static defaultProps = { title: '我是详情' } }
使用propTypes进行类型检查
PropTypes 提供一系列验证器,可用于确保组件接收到的数据类型是有效的。当传入的 prop 值类型不正确时,JavaScript 控制台将会显示警告。
propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps。
出于性能方面的考虑,propTypes 仅在开发模式下进行检查。
// 只要给组件加上 propTypes 属性,React 就会认为是在加规则 Person.propTypes = { // React.PropTypes 是 React 内置的属性 title: React.PropTypes.string.isRequired, // 错误 // 自 React v15.5 起,React.PropTypes 已移入另一个包中,需要的话要使用`import PropTypes from 'prop-types'`引入,引入之后全局就会有了一个对象 PropTypes title: PropTypes.string.isRequired, // 正确 speak: PropTypes.func, } /// 简写:简写的这种方式只适用于类组件,因为函数式组件中是没有 static 的 class Person extends React.Component{ static propTypes = { title: PropTypes.string.isRequired, } }
组件实例对象的三大核心属性-Refs
PS:勿过度使用 Refs
组件内的标签可以定义 ref 属性来标识自己,都会被收集到组件实例对象的 refs 属性下,这样,通过 this.refs.ref属性 就可以访问到 ref 当前所处的真实节点。
无法在函数式组件上使用 ref 属性。
Ant Design 中很多组件都获取不到 ref,可以包裹或内嵌一层自己创建的元素以获取 ref。
字符串形式的Ref
React 不推荐使用字符串形式的 ref,它已过时并可能会在未来的版本中被移除,这种方式存在一些效率上的问题。
class Demo extends React.Component { showData = () => { console.log(this) // 打印可以看到组件的实例对象上 this 有 refs 属性,属性值是 key-value 的对象 ,其中有一个key 就是 input1,value 是 ref 当前所处的真实节点。 // 访问 refs alert(this.res.input1.value) } render() { return ( <div> // 创建、绑定 refs <input ref="input1" /> <button onClick={this.showData}>点击</button> </div> ) } }
回调函数形式的Ref
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,第二次才会传入 DOM 元素。这是因为在每次 render 渲染时都会创建一个新的函数实例,所以 React 会首先清空旧的 ref,然后才会设置新的。这个问题大多数情况下是无关紧要的。
初次渲染时不会,因为初次渲染时没有旧的 ref 需要去清空。
class Demo extends React.Component { showData = () => { // 访问 refs alert(this.input1.value) } render() { return ( <div> // 创建、绑定 refs // render 方法执行的时候会自动调用 ref 的回调函数,并且会把当前所处的真实节点作为参数传递进去,然后将这个节点赋值给组件实例自身的一个自定义属性上 <input ref={c => this.input1 = c} /> <button onClick={this.showData}>点击</button> </div> ) } }
通过将 ref 的回调函数定义成类的绑定函数的方式可以避免上述问题。
class Demo extends React.Component { setInputRef = (c) => { // 绑定 refs this.input1 = c } showData = () => { // 访问 refs alert(this.input1.value) } render() { return ( <div> // 创建 refs // 更新时也不会重复触发 setInputRef,因为它已经放在实例自身了 <input ref={this.setInputRef} /> <button onClick={this.showData}>点击</button> </div> ) } }
createRef
React.createRef() 是 React 内置的一个 API,调用后可以返回一个容器,该容器存储被 ref 所标识的节点。该容器是专人专用的。
class Demo extends React.Component { // 创建 refs myRef = React.createRef() showData = () => { // 访问 refs alert(this.myRef.current.value) } render() { return ( <div> // 绑定 refs // 下面一行代码在执行的时候,React 发现了 ref 属性,并且发现属性值是用 createRef 创建出来的一个容器,这时, React 会把当前 ref 所在的那个节点直接存储到那个容器里面 <input ref={this.myRef} /> <button onClick={this.showData}>点击</button> </div> ) } }
访问Refs
当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。
ref 的值根据节点
Refs 转发
Refs 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递给子组件。
ref 转发不仅限于 DOM 组件,也可以转发 refs 到 class 组件实例。
const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>; const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> ));
FancyButton 使用 React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM button。这样,使用 FancyButton 的组件可以获取底层 DOM 节点 button 的 ref ,并在必要时访问,就像其直接使用 DOM button 一样。
上述代码的执行步骤如下:
- 通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量;
- 通过指定 ref 为 JSX 属性,将其向下传递给
<FancyButton ref={ref}>
; - React 传递 ref 给 forwardRef 内函数
(props, ref) => ...
,作为其第二个参数; - 向下转发该 ref 参数到
<button ref={ref}>
,将其指定为 JSX 属性; - 当 ref 挂载完成,ref.current 将指向
<button>
DOM 节点;
到此这篇关于React组件实例三大核心属性State props Refs详解的文章就介绍到这了,更多相关React组件state props refs内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!