寫了一段時間的 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 是啥
可以看出,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} /> ) } } |
那既然可以獲取到子組件的實例,我們就可以操作子組件了,比如文章最開始說,我想在父組件里去撈子組件的某些狀態值。
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} /> // 但是放在函數組件上無效 } |
那函數組件就不能使用 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} /> } |
這里貼一下官網的 tip:
那既然函數組件也可以使用 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