1.refs 是什么?
- Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素;
- 官方说明:在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法。
- 通俗来说,提供了一个父组件用来访问子组件中数据或者方法的桥梁
2.refs 实战
2.1 class组件中使用refs (父子组件都是class)
获取DOM节点
import { Button } from 'antd';
export default class RefClass extends Component {
constructor(props){
super(props)
this.myRef = createRef()
this.changeInput = this.changeInput.bind(this);
}
changeInput(){
console.log('this.myRef',this.myRef.current)
console.log('this.myRef',this.myRef.current.value)
}
render() {
return (
<div >
RefClass
<input ref={this.myRef} />
<Button type="primary" onClick={this.changeInput}>
Submit
</Button>
</div>
)
}
}
父组件访问子组件中的数据和方法
父组件
import Child from './Child'
import { Button } from 'antd';
export default class RefClass extends Component {
constructor(props){
super(props)
this.myRef = createRef()
this.changeInput = this.changeInput.bind(this);
this.childRef = createRef()
}
changeInput(){
console.log(this.childRef.current.state.name)
// 调用子组件的方法
this.childRef.current.updateState(this.myRef.current.value)
}
render() {
return (
<div >
RefClass
<input ref={this.myRef} />
<Child ref={this.childRef} />
<Button type="primary" onClick={this.changeInput}>
Submit
</Button>
</div>
)
}
}
子组件
import React, { Component } from 'react'
export default class Child extends Component {
constructor(props){
super(props)
this.state={
name:'我是子组件'
}
}
updateState(msg){
this.setState({
name:msg
})
}
render() {
return (
<div>
{this.state.name}
</div>
)
}
}
2.2 hooks中年中的refs的使用(父子组件都是hooks)
获取DOM 元素
import { Button } from 'antd';
const RefHooks = () => {
const childRef = useRef(null)
const changeInput = ()=>{
console.log('changeInput',childRef.current.value)
}
return (
<div>
<div>RefHooks</div>
<input ref={childRef} />
<Button type="primary" onClick={changeInput}>
Submit
</Button>
</div>
)
}
export default RefHooks
父组件访问子组件的方法或数据
注:引入 useImperativeHandle 把使用 ref 父组件访问子组件的数据与方法暴露出来 且 useImperativeHandle 应当与 forwardRef 一起使用
父组件
import { Button } from 'antd';
import Child from './Child'
const RefHooks = () => {
const inputRef = useRef(null)
const childRef = useRef(null)
const changeInput = ()=>{
console.log('changeInput',inputRef.current.value)
childRef.current.changText('我是父组件修改的子组件')
}
return (
<div>
<div>RefHooks</div>
<input ref={inputRef} />
<Child ref={childRef}/>
<Button type="primary" onClick={changeInput}>
Submit
</Button>
</div>
)
}
export default RefHooks
子组件
const Child = (props,ref) => {
const [state, setstate] = useState('我是子组件')
const changText = (e)=>{
setstate(e)
}
useImperativeHandle(ref, () => ({
changText
}));
return (
<div>
{state}
</div>
)
}
export default forwardRef(Child)
注:
- 子组件要使用
forwardRef进行包裹,useImperativeHandle中的方法只暴露给父组件使用,组件自身无法调用,两者都想使用,如上写法; - 子组件接受ref时,也要接受props,否则会报错
2.3 refs 在高阶组件中的使用
calss高阶组件类型
思路:定义好ref 以别名的方式传入HOC组件中,在调用HOC中导出的组件 添加ref
父组件
import { Button } from 'antd'
import HocIndex from './HocIndex'
import User from './User'
const HocUser = HocIndex(User);
export default class RefHOC extends Component {
constructor(props) {
super(props)
this.classHocRef = createRef(null)
this.inputRef = createRef(null)
this.handClick = this.handClick.bind(this)
}
handClick(){
this.classHocRef.current.updatFn(this.inputRef.current.value)
}
render() {
return (
<div>
<HocUser name={'name'} refText={this.classHocRef} />
<input ref={this.inputRef} />
<Button type="primary" onClick={this.handClick}>
Submit
</Button>
</div>
)
}
}
高阶组件
function HocIndex(WrappedComponent) {
return class WrappingComponent extends React.Component {
render() {
const props ={...this.props,ref:this.props.refText}
return (
<WrappedComponent {...props} />
)
}
}
}
export default HocIndex;
子组件
export default class User extends Component {
constructor(props){
super(props)
this.state={
name:'我是Hoc组件'
}
}
updatFn(e){
this.setState({
name:e
})
}
render() {
return (
<div>{this.state.name}</div>
)
}
}
hooks高阶组件类型
父组件
import React, { useRef } from 'react'
import HooksUser from './HooksUser';
import { Button } from 'antd'
const Index = () => {
const hocRef = useRef(null);
const changeInput=()=>{
hocRef.current.changText('父组件修改子组件')
}
return (
<div>
我是hooks 组件
<HooksUser ref={hocRef} name='user' />
<div>
<Button type="primary" onClick={changeInput}>
Submit
</Button>
</div>
</div>
)
}
export default Index
高阶组件
import React,{forwardRef} from 'react'
const HookHoc = Component => {
const HocIndex = ({hocRef,...props})=> <Component ref={hocRef} {...props} />
return forwardRef((props,ref)=> <HocIndex {...props} hocRef={ref}/>)
}
export default HookHoc
子组件
import HookHoc from './HookHoc'
const HooksUser = (props,ref) => {
const [state, setstate] = useState('我是hooks 子组件');
const changText = (e)=>{
setstate(e)
}
useImperativeHandle(ref, () => ({
changText
}));
return (
<div>
{state}
</div>
)
}
export default HookHoc(forwardRef(HooksUser))
2.4 回调的方式设置refs
- 能够更准确的控制何时refs 被设置和解除
React会在组件挂载时,调用ref回调函数并传入DOM元素(或React实例),当卸载时调用它并传入 null。在componentDidMount或componentDidUpdate触发前,React 会保证 Refs 一定是最新的。- 可以在组件间传递回调形式的 refs.
class类型组件
import { Button } from 'antd'
export default class index extends Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
if (this.textInput) this.textInput.focus();
console.log('this.textInput',this.textInput.value)
};
}
componentDidMount() {
this.focusTextInput();
}
render() {
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<Button type="primary" onClick={this.focusTextInput}>
Submit
</Button>
</div>
);
}
}
hooks类型组件
import { Button } from 'antd';
import React, { useEffect } from 'react';
function MyInput(props) {
return (
<input type="text" ref={props.inputRef} />
)
}
const HooksFnRef = () => {
let ref = null;
useEffect(() => {
ref.focus();
console.log('ref', ref)
}, [ref]);
const getInputVlaue = () => {
console.log(ref.value)
}
return (
<div>
<MyInput inputRef={ele => ref = ele} />
<Button type="primary" onClick={getInputVlaue}>
Submit
</Button>
</div>
)
}
export default HooksFnRef
3.refs中遇到的一个小坑
回调refs 存在 如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
4.总结
- ref内容发生改变时,组件不会进行render;可以通过调用子组件的方法来实现需要的子组件进行更新,减少其余组件的渲染;
- 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现
- 官方不建议滥用refs,尽量减少项目中refs的使用
写的若有问题欢迎各位道友直接指出。在此感激不进;