React 学习笔记2 props、refs
props
基础用法
在使用组件时,有时希望组件中展示组件定义外部的数据。
在组件上有一个props属性,在通过类式方法定义组件时,可以在渲染组件时,在组件标签上添加属性,这个属性会以key value的形式放入props。
<body><div id="demo"></div><script src="https://unpkg.com/react@17/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script><!-- Don't use this in production: --><script src="https://unpkg.com/@babel/standalone/babel.min.js"></script><script type="text/babel">class Cat extends React.Component {render() {return <h2>{this.props.name}--{this.props.color}</h2>}}ReactDOM.render(<Cat name="cat" color="orange"/>, document.getElementById('demo'))</script>
</body>
props批量传递
当这种传参方式也存在问题,一个问题是,当需要传入的参数过多时,很难每个都手写。一个解决办法是使用...语法:
实际上,对于对象类型来说,...展开运算符是无法直接应用在对象上的,但如果使用{...对象}的形式是可以的,相当于是在构造字面量对象时使用展开语法,相当于克隆了对象,而且是深拷贝。通过{...对象,要修改的value对应的key:新value}来进行修改,会对这些同名key进行合并。
而在React的标签中,书写{...对象}时,外部的{}并不是字面量克隆,而是表示内部的语法是JS表达式,相当于...对象,这是React内部允许的特殊写法,通过React+Babel,支持使用...对对象进行展开。但这种语法并不是在任意位置都会生效,这种语法仅仅适用于标签参数的传递。
<script type="text/babel">class Cat extends React.Component {render() {return <h2>{this.props.name}--{this.props.color}</h2>}}const data = {name:'cat',color:"orange",age:10}ReactDOM.render(<Cat {...data}/>, document.getElementById('demo'))</script>
限制props
通过属性传递key value时,value默认是字符串,如果需要在标签中传递数字,需要通过key={数字}的形式来进行,但可能代码是很多人书写的,有时候其他人并不会考虑后续使用的各种情况,这会导致代码产生各种各样的问题。如果希望传递的属性是某个固定的数值,就需要对props添加限制。
React支持对props的 数据类型、数据默认值、数据是否必须传递 进行限制。
这要通过给组件添加propTypes属性来实现。
propTypes的value是一个对象,通过在对象里添加配置,React可以给props添加限制。
通过key:PropTypes.需要传递的类型(小写)的形式可以对props的类型进行限制。通过.isRequired可以设置属性为必填项。通过对类加上defaultProps可以为属性设置默认值,defaultProps的value为对象,对象内部以key:默认值的形式进行设置。通过标签也能传递函数,函数的数据类型是func。
propTypes是需要限制props时,应该给类式组件添加的属性,而PropTypes是支持设置限制的方法,两者并不相同。
PropTypes并不是React内置的方法,需要npm install prop-types及import PropTypes from 'prop-types'来引入PropTypes。
npm install prop-typesimport PropTypes from 'prop-types'
而对于直接在HTML文件中编写的React Demo,可以通过js的方式进行引入:
<script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
<body><div id="demo"></div><script src="https://unpkg.com/react@17/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone/babel.min.js"></script><script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script><script type="text/babel">class Cat extends React.Component {render() {return <h2>{this.props.name}--{this.props.color}--{this.props.say()}</h2>}}Cat.propTypes = {name: PropTypes.string.isRequired,age: PropTypes.number,say: PropTypes.func}function say(){console.log(this.name)}const data = { name: 'cat', color: "orange", age: 10 }ReactDOM.render(<Cat {...data} say={say} />, document.getElementById('demo'))</script>
</body>
props是只读的,通过props传递的参数,在类内部是无法修改的。
props简写形式:
组件类.propTypes和组件类.defaultProps可以不在类外面编写,可以直接在类内部定义:
<script type="text/babel">class Cat extends React.Component {static propTypes = {name: PropTypes.string.isRequired,age: PropTypes.number,say: PropTypes.func}static defaultProps = {color: 'black'}render() {return <h2>{this.props.name}--{this.props.color}--{this.props.say()}</h2>}}function say() {console.log(this.name)}const data = { name: 'cat', age: 10 }ReactDOM.render(<Cat {...data} say={say} />, document.getElementById('demo'))</script>
构造器中的props
在通过构造器对属性进行操作时,构造器会有一个参数props,且在构造器内部需要super(props)。
为什么需要在构造器中编写props相关的代码呢?这是为了防止this.props调用时出现属性未定义的BUG。
类中构造器其实也可以不写props,也不会报错,但此时通过this.props访问得到的是undefined。如果不需要访问this.props,也可以完全不写props和super(props)。
函数式组件的props
对于函数式组件,虽然函数式组件中没有this,也没有state(可以通过hooks间接实现)和refs,但是函数式组件中有Props。
使用函数式组件时,用法和类式类似,也是在标签中编写要传递的数据,在函数中,通过参数props获得传递的数据。
函数式组件也可以对props进行限制,语法和类式组件类似,只不过函数式组件对props的限制只能写在函数外面。
<script type="text/babel">function Cat(props) {return <h2>{props.name}--{props.color}--{props.say()}</h2>}Cat.propTypes = {name: PropTypes.string.isRequired,age: PropTypes.number,say: PropTypes.func}Cat.defaultProps = {color: 'black'}function say() {console.log(this.name)}const data = { name: 'cat', age: 10 }ReactDOM.render(<Cat {...data} say={say} />, document.getElementById('demo'))</script>
refs
ref的value有三种形式:
字符串形式
ref是React中提供的一个属性,给标签加入ref属性后,组件实例上的refs对象中会出现这个ref,refs中的key是标签中给ref设定的value,refs对应的value是ref属性所在的标签。设置好ref后,通过this.refs.给ref设定的value,可以获取ref所在的标签。
通过refs获取的是真实DOM节点。
这种方式不被React推荐,在之后的版本中可能会被移除。字符串类型的ref存在效率问题。
<script type="text/babel">class Cat extends React.Component {alertData=()=>{const nowDOM = this.refs.inputData;alert(nowDOM.value)}render(){return (<div><input ref="inputData"/><button onClick={this.alertData}>click</button></div>)}}const data = { name: 'cat', age: 10 }ReactDOM.render(<Cat />, document.getElementById('demo'))</script>
回调函数形式
ref的value,也可以是一个回调函数。由于函数是JS表达式,需要包裹在{}中,这个回调函数的参数是ref属性所处的DOM,在函数体中,一般把这个属性放在组件实例上。
<script type="text/babel">class Cat extends React.Component {alertData=()=>{const nowDOM = this.inputDOMalert(nowDOM.value)}render(){return (<div><input ref={ (currentDOM)=>{this.inputDOM = currentDOM} }/><button onClick={this.alertData}>click</button></div>)}}ReactDOM.render(<Cat />, document.getElementById('demo'))</script>
回调函数在什么时候被调用呢?实际上,在初始时,这个回调函数会被调用一次。除此之外,如果ref是以内联函数形式定义的,在更新时(初始化加载时不是更新,更新是初始化之后,数据改变引起的更新)回调函数会被执行两次,第一次参数是null,第二次参数才是ref所在的DOM元素。这是因为在每次渲染时会创建一个新的函数实例,React在更新时,会清空旧的ref并设置新的。
<script type="text/babel">class Cat extends React.Component {alertData=()=>{const nowDOM = this.inputDOMalert(nowDOM.value)}state={flag:true,}changeFlag=()=>{const flag = this.state.flagthis.setState( {flag : !flag})}render(){return (<div><input ref={ (currentDOM)=>{this.inputDOM = currentDOM;console.log(currentDOM)} }/><button onClick={this.alertData}>click</button><button onClick={this.changeFlag}>change</button><h2>{this.state.flag ? 'cat':'dog'}</h2></div>)}}ReactDOM.render(<Cat />, document.getElementById('demo'))</script>
但这种情况一般并不会影响ref的功能,仅仅是会多调用一次。如果还是想避免这种情况,可以不使用内联函数,而是使用类内部绑定的函数。
<script type="text/babel">class Cat extends React.Component {alertData = () => {const nowDOM = this.inputDOMalert(nowDOM.value)}state = {flag: true,}changeFlag = () => {const flag = this.state.flagthis.setState({flag: !flag})}getInputDOM = (currentDOM) => {this.inputDOM = currentDOMconsole.log(currentDOM)}render() {return (<div><input ref={this.getInputDOM} /><button onClick={this.alertData}>click</button><button onClick={this.changeFlag}>change</button><h2>{this.state.flag ? 'cat' : 'dog'}</h2></div>)}}ReactDOM.render(<Cat />, document.getElementById('demo'))</script>
JSX的注释以{/* */}的形式进行。{}表示要写JS表达式,内部的/**/是JS注释。
React.createRef()
ref的value,也可以通过React.createRef()来设定。
React.createRef是一个函数,调用之后会返回一个容器,可以存储被ref所标识的DOM节点。
当ref的value是createRef容器时,会把ref所在的DOM节点存储在容器中。且是存储在容器的current属性中。current中只能存储一个DOM。如果多个ref存储在一个容器中,后触发的存储会覆盖之前的。如果需要获取多个DOM,只能创建多个容器。
<script type="text/babel">class Cat extends React.Component {alertData = () => {const nowDOM = this.inputRef.currentalert(nowDOM.value)}inputRef = React.createRef()buttonRef = React.createRef()render() {return (<div><input ref={this.inputRef} /><input ref={this.inputRef} /><button onClick={this.alertData} ref={this.buttonRef}>click</button></div>)}}ReactDOM.render(<Cat />, document.getElementById('demo'))</script>