服务器之家

服务器之家 > 正文

React ref的使用示例

时间:2022-02-25 16:29     来源/作者:陈小瓦

写了一段时间的 react,99%都在写 state、prop、useState、useEffect,对 ref 特别不熟悉,前几天做一个需求,想用 ref 实现父组件捞子组件的某个状态值,结果失败了,特此整理一下 ref 相关内容。

什么是 ref

官网介绍:

在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法,即使用 ref 来获取 dom 或组件实例。

如何使用 ref

放在 dom 元素上

这是 ref 最直接的用法

?
1
2
3
4
5
6
7
8
9
10
11
12
export class Demo extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = createRef()
  }
  componentDidMount() {
    console.log(this.myRef)
  }
  render() {
    return <div ref={this.myRef}>测试</div>
  }
}

打印看一下 ref 是啥

React ref的使用示例

可以看出,ref.current 拿到了 dom 元素,所以我们可以实现 dom 元素本身的一些功能,如 input 的聚焦:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export class Demo extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = createRef()
  }
 
  onClick = () => {
    this.myRef.current.focus()
  }
 
  render() {
    return (
      <div>
        <button onClick={this.onClick}>聚焦</button>
        <input ref={this.myRef} />
      </div>
    )
  }
}

官网还提供了一种 ref 回调的形式:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export class Demo extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = null
  }
 
  onClick = () => {
    this.myRef.focus()
  }
 
  render() {
    return (
      <div>
        <button onClick={this.onClick}>聚焦</button>
        <input ref={ele => this.myRef = ele} /> // 这里的 ele 就是该 dom 元素
      </div>
    )
  }
}

放在类组件上

其实组件跟原生 dom 差不多,也是拥有自己的 ui、一些功能的某种元素,所以将 ref 放在组件上,也可以获取到该组件的示例。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 子组件
class Child extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'xx'
    }
  }
  render() {
    return <div>子元素{this.state.name}</div>
  }
}
 
export class Demo extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = createRef()
  }
 
  componentDidMount() {
    console.log(this.myRef)
  }
 
  render() {
    return (
     <Child ref={this.myRef} />
    )
  }
}

React ref的使用示例

那既然可以获取到子组件的实例,我们就可以操作子组件了,比如文章最开始说,我想在父组件里去捞子组件的某些状态值。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Child extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
  }
  
  onClick = () => {
    this.setState({count: this.state.count+1})
  }
 
  render() {
    return <button onClick={this.onClick}>点击+1:{this.state.count}</button>
  }
}
 
export class Demo extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = createRef()
  }
 
  onClick = () => {
    console.log(this.myRef.current.state.count) // 拿到子组件的状态值
  }
 
 
  render() {
    return (
      <div>
        <button onClick={this.onClick}>获取子组件的点击次数</button>
        <Child ref={this.myRef} /> // ref 获取到子组件实例
      </div>
    )
  }
}

既然能拿值,我也能拿函数去修改子组件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Child extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'xx'
    }
  }
  changeName = () => {
    this.setState({name: 'ww'})
  }
  render() {
    return <div>子元素{this.state.name}</div>
  }
}
 
export class Demo extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = createRef()
  }
 
  onClick = () => {
    this.myRef.current.changeName() // 父组件的手伸到子组件里去啦
  }
 
  render() {
    return (
      <div>
        <button onClick={this.onClick}>改变子组件的状态</button>
        <Child ref={this.myRef} />
      </div>
    )
  }
}

当然这个例子并不恰当,父组件想更改子组件的状态的话,应该把状态提升到父组件中,然后作为子组件的props传递进去。
主要是 ref 提供一种方式去绕过 props 来实现父子组件通信。

放在函数组件上

这是我文章开头写需求时犯的错,ref 不能放在函数组件上,因为函数组件没有实例。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
const Child = () => {
  return <div>子组件</div>
}
 
export const Demo = () => {
  const myRef = useRef() // 可以在函数组件内创建 ref
 
  useEffect(() => {
    console.log(myRef)
  }, [])
 
  return <Child ref={myRef} /> // 但是放在函数组件上无效
}

React ref的使用示例

那函数组件就不能使用 ref 了吗,那肯定不是哈哈。我们可以使用 forwardRef 包装函数组件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Child = (props, ref) => { // 包装后,除了原有的 props 外, ref 也被传了进来
  return <div ref={ref}>子组件</div> // 还是得挂载到 dom 上
}
 
const ProChild = React.forwardRef(Child) // 重点在这里
 
export const Demo = () => {
  const myRef = useRef()
 
  useEffect(() => {
    console.log(myRef)
  }, [])
 
  return <ProChild ref={myRef} />
}

React ref的使用示例

这里贴一下官网的 tip:

React ref的使用示例

那既然函数组件也可以使用 ref 的话,我们用函数组件实现一下父组件捞子组件的数据,不过可以看出,使用 forwardRef 包裹后,ref 还是得挂载到 dom 或者类组件上,如果我只想挂载数据还需要搭配 useImperativeHandle。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const Child = (props, ref) => {
  const [count, setCount] = useState(0)
 
  useImperativeHandle(
    ref,
    () => ({   // 这里就是暴露给外部 ref 的数据
      getVal: ()=> count
    }),
    [count],
  )
 
  const onClick = () => {
    setCount(pre => pre+1)
  }
  return <button onClick={onClick}>点击+1:{count}</button>
}
 
const ProChild = React.forwardRef(Child)
 
export const Demo = () => {
  const myRef = useRef()
 
  const onClick = () => {
    console.log(myRef.current.getVal()) // 拿到子组件的值
  }
 
  return <><button onClick={onClick}>获取子组件的点击次数</button><ProChild ref={myRef} /></>
}

至此完成了做需求时留下的问题 ✅

总结

最后还是需要强调一下,父组件获取子组件状态的场景,一般还是状态提升 + 回调来通信,需求最终也是使用这种方式来实现的,最开始之所以想用 ref,是觉得状态提升后,子组件变化了会引起父组件的重新渲染,但是我只想拿数据而不引起渲染。
跟师傅说了一下我写需求时的想法,师傅见解如下:

  • 优先考虑状态提升
  • 有性能问题的话,考虑状态提升 + memo
  • 不想给多个组件加 memo 的话,就要考虑引入 redux/mobx 了
  • 如果引入 redux/mobx 是一种成本的话,那 ref 也不是不可以哈哈哈

以上就是React ref的使用详解的详细内容,更多关于React ref的使用的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/6948422543288041503

标签:

相关文章

热门资讯

2022年最旺的微信头像大全 微信头像2022年最新版图片
2022年最旺的微信头像大全 微信头像2022年最新版图片 2022-01-10
蜘蛛侠3英雄无归3正片免费播放 蜘蛛侠3在线观看免费高清完整
蜘蛛侠3英雄无归3正片免费播放 蜘蛛侠3在线观看免费高清完整 2021-08-24
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
返回顶部