React组件实例三大核心属性State props Refs详解
花铛 人气:0组件组件实例的三大核心属性-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 节点;
加载全部内容