React Hook详解 - useState、useEffect、useContext、useRef、自定义hook

文章目录

  • 一、useState:让函数式组件拥有状态
    • 用法示例
    • 函数式更新
    • 两种方式的区别
  • 二、useEffect:副作用,取代生命周期
    • useEffect的使用
    • useEffect的生命周期
    • useEffect的第二个参数
    • 清理副作用
  • 三、useContext:跨组件共享数据
    • useContext是什么
    • useContext作用
    • useContext的使用
    • 举例
      • 1. 创建父组件
      • 2. 创建子组件
  • 四、useCallback:性能优化
    • useCallback使用场景
    • useCallback依赖state
  • 五、useMemo:性能优化
  • 六、useRef
    • 什么是useRef
      • 示例
    • 为什么使用useRef
    • 获取子组件的属性或方法
      • 自定义属性传入ref
      • 通过useImperativeHandle,配合forwardRef

一、useState:让函数式组件拥有状态

const [state, setState] = useState(initialState);

返回一个state,以及更新state的函数
在初始渲染期间,返回的状态(state)与传入的参数initialState的值相同。
setState用于更新state,接收一个新的state值并将组件的一次重新渲染加入队列。

setState(newState)

用法示例

import {useState} from 'react';

const Test = () =>{
	const [count, setCount] = useState(0);
	
	return (
		<>
			<h1>点击了{count}</h1>
			<button
				onClick={
					() => setCount(count + 1)
				}>
				+1
			</button>
		</>
	);
}

export default Test;

在class组件中,this.setState更新是state合并,useStatesetState是替换。

例如:

// 错误示例
import { useState } from 'react';

const Test = () => {
    const [counts, setCounts] = useState({
        num1: 0,
        num2: 0
    });
    
    return (
        <>
            <h1>num1:{counts.num1}</h1>
            <h1>num2:{counts.num2}</h1>
            <button onClick={() => setCounts({ num1: counts.num1 + 1})}>num1+1</button>
            <button onClick={() => setCounts({ num2: counts.num2 + 1})}>num2+1</button>
        </>
    );
}

export default Test;

可以看到useStatesetState是替换,不会合并,正确更新:

import { useState } from 'react';

const Test = () => {
    const [counts, setCounts] = useState({
        num1: 0,
        num2: 0
    });
    
    return (
        <>
            <h1>num1:{counts.num1}</h1>
            <h1>num2:{counts.num2}</h1>
            <button onClick={() => setCounts({ ...counts, num1: counts.num1 + 1})}>num1+1</button>
            <button onClick={() => setCounts({ ...counts, num2: counts.num2 + 1})}>num2+1</button>
        </>
    );
}

export default Test;

函数式更新

如果新的state需要通过使用先前的state计算得出,那么可以将函数传递给 setState
该函数将接收先前的 state,并返回一个更新后的值。

下面的计数器组件示例展示了setState的两种用法:

function Counter() {
	const [count, setCount] = useState(0);

	function handleClick() {
		setCount(count + 1);
	}

	function handleClickFn() {
		setCount((prevCount) => {
			return prevCount + 1;
		})
	}
	 
	return (
		<>
			Count: {count}
			<button onClick={handleClick}>+</button>
			<button onClick={handleClickFn}>+</button>
     </>
   );
}

两种方式的区别

  • handleClick通过一个新的 state 值更新。
  • handleClickFn通过函数式更新返回新的 state。

在同步更新时,两种方法没有区别。如果是异步更新则是有区别的。

function Counter() {
  const [count, setCount] = useState(0);
  
  function handleClick() {
    setTimeout(() => {
      setCount(count + 1)
    }, 3000);
  }
  
  function handleClickFn() {
    setTimeout(() => {
      setCount((prevCount) => {
        return prevCount + 1
      })
    }, 3000);
  }
  
  return (
    <>
      Count: {count}
      <button onClick={handleClick}>+</button>
      <button onClick={handleClickFn}>+</button>
    </>
  );
}

当设置为异步更新时,点击按钮延迟到3s之后去调用setCount函数。当进行快速点击时,即在3s内多次触发更新,但是只有一次是生效的,因为count的值并没有发生变化

当使用函数式更新 state 的时,则不会出现这种情况。因为它可以获取之前的 state 值,也就是代码中的 prevCount 每次都是最新的值。

这与类组件中的setState类似,可以接收一个新的 state 值更新,也可以函数式更新。如果新的 state 需要根据之前的 state 计算得出,则需要使用函数式更新。

因为setState更新可能是异步,当你在事件绑定中操作 state 的时候,setState更新就是异步的。

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
  }
  
  handleClick = () => {
    this.setState({ count: this.state.count + 1 })
    this.setState({ count: this.state.count + 1 })
    // 这样写只会加1
  }
  
  handleClickFn = () => {
    this.setState((prevState) => {
      return { count: prevState.count + 1 }
    })
    this.setState((prevState) => {
      return { count: prevState.count + 1 }
    })
  }
  
  render() {
    return (
      <>
        Count: {this.state.count}
        <button onClick={this.handleClick}>+</button>
        <button onClick={this.handleClickFn}>+</button>
      </>
    );
  }
}

当你在定时器中操作 state 的时候,而 setState 更新就是同步的。

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
  }
  
  handleClick = () => {
    setTimeout(() => {
      this.setState({ count: this.state.count + 1 })
      this.setState({ count: this.state.count + 1 })
      // 这样写是正常的,两次setState最后是加2
    }, 3000);
  }
  
  handleClickFn = () => {
    this.setState((prevState) => {
      return { count: prevState.count + 1 }
    })
    this.setState((prevState) => {
      return { count: prevState.count + 1 }
    })
  }
  
  render() {
    return (
      <>
        Count: {this.state.count}
        <button onClick={this.handleClick}>+</button>
        <button onClick={this.handleClickFn}>+</button>
      </>
    );
  }
}

注意这里的同步和异步指的是 setState 函数。因为涉及到 state 的状态合并,react 认为当你在事件绑定中操作 state 是非常频繁的,所以为了节约性能 react 会把多次 setState 进行合并为一次,最后在一次性的更新 state,而定时器里面操作 state 是不会把多次合并为一次更新的。

二、useEffect:副作用,取代生命周期

useEffect的使用

useEffect可以在函数组件中执行副作用操作。
副作用:除了状态相关的逻辑,如网络请求、监听事件、查找dom

例子
需求:当组件状态更新时改变document.title

普通写法:

class App extends React.Component{
	state = {
		count: 0
	}
	
	componentDidMount() {
		document.title = count;
	}
	
	componentDidUpdate() {
		document.title = count;
	}
	
	render () {
		const {count} = this.state;
		return (
			<div>
				页面名称:{count}
				<button onClick={() => { this.setState({ count: count++ })}}>click</button>
			</div>
		)
	}
}

hook写法:

function App () {
	const [count, setCount] = useState(0);
	
	useEffect(() => {
		document.title = count;
	});
	
	return (
		<div>
				页面名称:{count}
				<button onClick={() => { this.setState({ count: count++ })}}>click</button>
		</div>
	);
}

useEffect的生命周期

可以将useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

以往我们在绑定事件、解绑事件、设定定时器、查找 dom 等操作的时候都是通过这三个生命周期函数来实现的,而useEffect会在组件每次render之后调用,相当于这三个生命周期函数,只不过可以通过传参来决定是否调用。

useEffect会返回一个回调函数,作用于清除上一次副作用遗留下来的状态,如果该useEffect只调用一次,该回调函数相当于componentWillUnmount函数。

例子

function App () {
  const [ count, setCount ] = useState(0);
  const [ width, setWidth ] = useState(document.body.clientWidth);

  const onChange = () => {
    setWidth(document.body.clientWidth);
  };

  useEffect(() => {
    window.addEventListener('resize', onChange, false);

    return () => {
      window.removeEventListener('resize', onChange, false);
    }
  });

  useEffect(() => {
    document.title = count;
  });

  return (
    <div>
      页面名称: { count } 
      页面宽度: { width }
      <button onClick={() => { setCount(count + 1)}}>点我</button>
    </div>
  );
}

在这个例子中,我们既要处理title,还要监听屏幕宽度。
在 hook 中,我们需要使用两个useEffect来解决问题。useEffect可以返回一个函数,用来清除上一次副作用留下的状态,这个地方可以用来解绑事件监听。但是存在一个问题:useEffect每次render后就会调用,比如title改变,相当于componentDidUpdate,但我们的事件监听不应该在每次的render之后进行一次绑定和解绑,也就是需要让useEffect变成componentDidMount,它的返回函数变成componentWillUnmount,这里需要用的useEffect的第二个参数。

useEffect的第二个参数

useEffect的第二个参数有三种情况:

  1. 什么都不传。 组件每次render之后 useEffect 都会调用,相当于 componentDidMountcomponentDidUpdate
  2. 传入一个空数组 [] 只会调用一次,相当于 componentDidMountcomponentWillUnmount
  3. 传入一个数组,其中包括变量,只有这些变量变动时,useEffect 才会执行
function App () {
  const [ count, setCount ] = useState(0);
  const [ width, setWidth ] = useState(document.body.clientWidth);

  const onChange = () => {
    setWidth(document.body.clientWidth);
  };

  useEffect(() => {
    // 相当于 componentDidMount
    console.log('add resize event');
    window.addEventListener('resize', onChange, false);

    return () => {
      // 相当于 componentWillUnmount
      window.removeEventListener('resize', onChange, false);
    }
  }, []);

  useEffect(() => {
    // 相当于 componentDidUpdate
    document.title = count;
  });

  useEffect(() => {
    console.log(`count change: count is ${count}`);
  }, [ count ]);

  return (
    <div>
      页面名称: { count } 
      页面宽度: { width }
      <button onClick={() => { setCount(count + 1)}}>点我</button>
    </div>
   );
} 

根据上面的例子的运行结果:

  • 第一个 useEffect 中的 'add resize event' 只会在第一次运行时输出一次,无论组件怎么 render,都不会在输出;
  • 第二个 useEffect 会在每次组件render之后都执行,title 每次点击都会改变;
  • 第三个 useEffect, 只要有在第一次运行和 count 改变时,才会执行,屏幕发生改变引起的render并不会影响第三个 useEffect

清理副作用

在上面的操作中都不用清理的副作用,然而,有些副作用是需要去清理的,不清理会造成异常甚至内存泄漏,比如开启定时器,如果不清理,则会多次开启,从上面可以看到useEffect的第一个参数是一个回调函数,可以在回调函数中再返回一个函数,该函数可以在状态更新后第一个回调函数执行之前调用,具体实现:

seEffect(() => {
    // 设置副作用
    return () => {
        // 清理副作用
    }
});

三、useContext:跨组件共享数据

useContext是什么

在Hooks之前,react开发者都是使用class组件进行开发,父子组件之间通过props传值。但是现在开始使用方法组件开发,没有constructor构造函数,也就没有了props的接收,所以父子组件的传值就成了一个问题。
于是,就有了useContext

useContext 的参数必须是 context 对象本身:

  • 正确useContext(MyContext)
  • 错误useContext(MyContext.Consumer)
  • 错误useContext(MyContext.Provider)

useContext作用

  1. 跨越组件层级直接传递变量,实现数据共享
  2. context 对所包含的组件树提供全局共享数据的一种技术。

useContext的使用

  1. 根组件导入并调用createContext方法,得到Context对象,并导出
    import { createContext } from 'react';
    export const Context = createContext();
    
  2. 在根组件中使用 Provider 组件包裹需要接收数据的后代组件,并通过 value 属性提供要共享的数据
    return(
    	<Context.Provider value={传递数据}>
    		<后代组件 />
    	</Provider>
    )
    
  3. 需要获取公共数据的后代组件:导入useContext,并按需导入根组件中导出的Context对象;调用useContext(第一步中导出的Context) 得到value的值
    import React, { useContext } from 'react';
    import { Context } from './index';
    const 函数组件 = () => {
    	const 公共数据 = useContext(Context);	//value中的值
    	return (函数组件内容);
    }
    

举例

1. 创建父组件

import React, { useContext, useState, createContext } from 'react';
import { Button } from 'antd';

const CountContext = createContext();

const App = () => {
  const [count, setCount] = useState(0);
  console.log(CountContext);
  console.log(useContext(CountContext));
  return (
    <div>
      <div>
        <p>父组件点击次数:{count}</p>
        <Button type={'primary'} onClick={() => setCount(count + 1)}>
          点击+1
        </Button>
        <CountContext.Provider value={count}>
          <Counter />
        </CountContext.Provider>
      </div>
    </div>
  );
};

export default App;

2. 创建子组件

const Counter = () => {
  const count = useContext(CountContext);
  console.log(CountContext);
  // console.log(count);
  // console.log(useContext(CountContext));
  return (
    <div>
      <p>子组件获得的点击数量:{count}</p>
    </div>
  );
};

React Hook详解 - useState、useEffect、useContext、useRef、自定义hook_第1张图片
React Hook详解 - useState、useEffect、useContext、useRef、自定义hook_第2张图片

四、useCallback:性能优化

// useCallback(回调函数,[依赖值])
const handleClick = useCallback(()=> {
    // 做一些事
}, [value]);

useCallback返回的是一个 memoized(缓存)函数,在依赖不变的情况下,多次定义的时候,返回的值是相同的,他的实现原理是当使用一组参数初次调用函数时,会缓存参数和计算结果,当再次使用相同的参数调用该函数时,会直接返回相应的缓存结果。

useCallback使用场景

例子:

import React, { useState, useEffect } from 'react';

let count = 0;

function App() {
  const [val, setVal] = useState('');

  function getData() {
    setTimeout(() => {
      setVal('new data' + count);
      count++;
    }, 500);
  };

  useEffect(()=>{
    getData();
  },[]);

  return <div>{val}</div>;
};

export default App;

getData模拟发起网络请求。在这种场景下,没有useCallback什么事,组件本身是高内聚的。

如果涉及到组件通讯,情况就不一样了:

import React, { useState, useEffect } from 'react';

let count = 0;

function App() {
  const [val, setVal] = useState('');

  function getData() {
    setTimeout(() => {
      setVal('new data' + count);
      count++;
    }, 500);
  }

  return <Child val={val} getData={getData} />;
}

export default App;

function Child({ val, getData }) {
  useEffect(() => {
    getData();
  }, [getData]);

  return <div>{val}</div>;
}

执行过程:

  1. App 渲染 Child,传入valgetData
  2. Child 使用useEffect获取数据。
  3. getData执行时,调用了setVal,导致 App 重新渲染
  4. App 重新渲染,使得产生新的getData,重新传入 Child
  5. getData引用改变,Child 再次使用getData
  6. 3->5为一个死循环

如果明确getData只执行一次,最简单的方式当然是将其从依赖列表中删除。但如果装了 hook 的lint 插件,会提示:React Hook useEffect has a missing dependency

useEffect(() => {
  getData();
}, []);

实际情况很可能是当getData改变的时候,是需要重新获取数据的。这时就需要通过useCallback来将引用固定住:

import React, { useState, useEffect, useCallback } from 'react';

let count = 0;

function App() {
  const [val, setVal] = useState('');

  const getData = useCallback(() => {
    setTimeout(() => {
      setVal('new data' + count);
      count++;
    }, 500);
  }, []);

  return <Child val={val} getData={getData} />;
}

export default App;

function Child({ val, getData }) {
  useEffect(() => {
    getData();
  }, [getData]);

  return <div>{val}</div>;
}

上面例子中getData的引用永远不会变,因为他它的依赖列表是空。可以根据实际情况将依赖加进去,就能确保依赖不变的情况下,函数的引用保持不变。

useCallback依赖state

假如在getData中需要用到val( useState 中的值),就需要将其加入依赖列表,这样的话又会导致每次getData的引用都不一样,死循环又出现了

import React, { useState, useEffect, useCallback } from 'react';

let count = 0;

function App() {
  const [val, setVal] = useState('');

  const getData = useCallback(() => {
    setTimeout(() => {
      console.log(val);

      setVal('new data' + count);
      count++;
    }, 500);
  }, [val]);

  return <Child val={val} getData={getData} />;
}

export default App;

function Child({ val, getData }) {
  useEffect(() => {
    getData();
  }, [getData]);

  return <div>{val}</div>;
}

如果我们希望无论val怎么变,getData的引用都保持不变,同时又能取到val最新的值,可以通过自定义 hook 实现。注意这里不能简单的把val从依赖列表中去掉,否则getData中的val永远都只会是初始值(闭包原理)。

 function useRefCallback(fn, dependencies) {
  const ref = useRef(fn);

  // 每次调用的时候,fn 都是一个全新的函数,函数中的变量有自己的作用域
  // 当依赖改变的时候,传入的 fn 中的依赖值也会更新,这时更新 ref 的指向为新传入的 fn
  useEffect(() => {
    ref.current = fn;
  }, [fn, ...dependencies]);

  return useCallback(() => {
    const fn = ref.current;
    return fn();
  }, [ref]);
}

使用

import React, { useState, useEffect, useCallback, useRef } from 'react';

let count = 0;

function App() {
  const [val, setVal] = useState('');

  const getData = useRefCallback(() => {
    setTimeout(() => {
      console.log(val);

      setVal('new data' + count);
      count++;
    }, 500);
  }, [val]);

  function useRefCallback(fn, dependencies) {
    const ref = useRef(fn);

    // 每次调用的时候,fn 都是一个全新的函数,函数中的变量有自己的作用域
    // 当依赖改变的时候,传入的 fn 中的依赖值也会更新,这时更新 ref 的指向为新传入的 fn
    useEffect(() => {
      ref.current = fn;
    }, [fn, ...dependencies]);

    return useCallback(() => {
      const fn = ref.current;
      return fn();
    }, [ref]);
  }

  return <Child val={val} getData={getData} />;
}

export default App;

function Child({ val, getData }) {
  useEffect(() => {
    getData();
  }, [getData]);

  return <div>{val}</div>;
}

五、useMemo:性能优化

例子:

import React, { useState } from 'react';

function App() {
  const [value, setValue] = useState(0);
  const [count, setCount] = useState(1);
  const getDoubleCount = () => {
    console.log('getDoubleCount进行计算');
    return (count * 2);
  };
  return (
    <div>
      <h2>value: {value}</h2>
      <h2>doubleCount: {getDoubleCount()}</h2>
      <button onClick={() => setValue(value + 1)}>value+1</button>
    </div>
  );
}
export default App;

可以看到getDoubleCount依赖的是count,但value发生变化它也重新进行了计算渲染,现在只需要将getDoubleCount使用useMemo进行包裹,如下:

import React, { useState, useMemo } from 'react';

function App() {
  const [value, setValue] = useState(0);
  const [count, setCount] = useState(1);
  const getDoubleCount = useMemo(() => {
    console.log('getDoubleCount进行计算');
    return count * 2;
  }, [count]);

  return (
    <div>
      <h2>value: {value}</h2>
      <h2>doubleCount: {getDoubleCount}</h2>
      <button onClick={() => setValue(value + 1)}>value+1</button>
    </div>
  );
}
export default App;

现在getDoubleCount只有依赖的count发生变化时才会重新计算渲染。

useMemo和useCallback的共同点:

接收的参数都是一样的,第一个是回调函数,第二个是依赖的数据
它们都是当依赖的数据发生变化时才会重新计算结果,起到了缓存作用

useMemo和useCallback的区别:

useMemo计算结果是return回来的值,通常用于缓存计算结果的值
useCallback计算结果是一个函数,通常用于缓存函数

六、useRef

什么是useRef

const refContainer = useRef(initialValue);
  • 返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )
  • 返回的 ref 对象在组件的整个生命周期内保持不变
  • 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方
  • 更新 useRef 是 side effect (副作用),所以一般写在 useEffectevent handler
  • useRef 类似于类组件的 this

示例

需求:点击button时选中文本框
实现

import React, { useRef, MutableRefObject } from 'react';

function App() {
  const inputEl = useRef(null);

  const handleFocus = () => {
    inputEl.current.focus();
  };

  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={handleFocus}>Focus the input</button>
    </div>
  );
}
export default App;

通过useRef定义inputEl变量,在 input 元素上定义ref = {inputEl},这样通过inputEl.current即可获取到 input DOM 元素,选中则调用focus函数。

为什么使用useRef

需求:跨渲染取状态值

只使用useState

import React, { useState } from 'react';

function App() {
  const [like, setLike] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert(`you clicked on ${like}`);
      //形成闭包,所以弹出来的是当时触发函数时的like值
    }, 3000);
  }

  return (
    <div>
      <button onClick={() => setLike(like + 1)}>{like}</button>
      <button onClick={handleAlertClick}>Alert</button>
    </div>
  );
}
export default App;

为什么不是界面上like的实时状态?
当我们更改状态的时候,React会重新渲染组件,每次的渲染都会拿到独立的like值,并重新定义个handleAlertClick函数,每个handleAlertClick函数体里的like值也是它自己的,所以当like为6时,点击alert,触发了handleAlertClick,此时的like是6,哪怕后面继续更改like到10,但alert时的like已经定下来了。

useRef

import React, { useRef } from 'react';

function App() {
  let like = useRef(0);
  function handleAlertClick() {
    setTimeout(() => {
      alert(`you clicked on ${like.current}`);
    }, 3000);
  }

  return (
    <div>
      <button
        onClick={() => {
          like.current = like.current + 1;
        }}
      >
        {like.current}</button>
      <button onClick={handleAlertClick}>Alert</button>
    </div>
  );
}
export default App;

获取子组件的属性或方法

自定义属性传入ref

import React, {
  useState,
  useEffect,
  useRef,
  useCallback,
  MutableRefObject,
} from 'react';

const ChildInput = (props) => {
  const { label, cRef } = props;
  const [value, setValue] = useState('');

  const handleChange = (e) => {
    const value = e.target.value;
    setValue(value);
  };

  const getValue = useCallback(() => {
    return value;
  }, [value]);

  useEffect(() => {
    if (cRef && cRef.current) {
      cRef.current.getValue = getValue;
    }
  }, [getValue]);

  return (
    <div>
      <span>{label}:</span>
      <input type="text" value={value} onChange={handleChange} />
    </div>
  );
};

function App() {
  const childRef = useRef({});

  const handleFocus = () => {
    const node = childRef.current;
    alert(node.getValue());
  };

  return (
    <div>
      <ChildInput label={'名称'} cRef={childRef} />
      <button onClick={handleFocus}>focus</button>
    </div>
  );
}

export default App;

通过useImperativeHandle,配合forwardRef

forwardRef: 将父类的ref作为参数传入函数式组件中
示例

React.forwardRef((props, ref) => {})  
//创建一个React组件,
//这个组件将会接受到父级传递的ref属性,
//可以将父组件创建的ref挂到子组件的某个dom元素上,
//在父组件通过该ref就能获取到该dom元素
const FancyButton = React.forwardRef((props, ref) => (  
  <button ref={ref} className="FancyButton">    
    {props.children}
  </button>
));
// 可以直接获取到button的DOM节点
const ref = React.useRef();
<FancyButton ref={ref}>Click me!</FancyButton>; 

useImperativeHandle: 在函数式组件中,用于定义暴露给父组件的ref方法,用来限制子组件对外暴露的信息,只有useImperativeHandle第二个参数定义的属性跟方法才可以在父组件获取到

为什么使用: 因为使用forward+useRef获取子函数式组件DOM时,获取到的dom属性暴露的太多了

解决: 使用 uesImperativeHandle 解决,在子函数式组件中定义父组件需要进行的 DOM 操作,没有定义的就不会暴露给父组件

useImperativeHandle(ref, createHandle, [deps]) // 第一个参数暴露哪个ref;第二个参数暴露什么信息
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
// 渲染  的父组件
// 可以调用 inputRef.current.focus()

实现

import React, {
  MutableRefObject,
  useState,
  useImperativeHandle,
  useRef,
  forwardRef,
  useCallback,
} from 'react';

let ChildInput = forwardRef((props, ref) => {
  const { label } = props;
  const [value, setValue] = useState('');
  // 作用: 减少父组件获取的DOM元素属性,只暴露给父组件需要用到的DOM方法
  // 参数1: 父组件传递的ref属性
  // 参数2: 返回一个对象,父组件通过ref.current调用对象中方法
  useImperativeHandle(ref, () => ({
    getValue,
  }));
  const handleChange = (e) => {
    const value = e.target.value;
    setValue(value);
  };
  const getValue = useCallback(() => {
    return value;
  }, [value]);
  return (
    <div>
      <span>{label}:</span>
      <input type="text" value={value} onChange={handleChange} />
    </div>
  );
});
const App = (props) => {
  const childRef = useRef({});
  const handleFocus = () => {
    const node = childRef.current;
    alert(node.getValue());
  };
  return (
    <div>
      <ChildInput label={'名称'} ref={childRef} />
      <button onClick={handleFocus}>focus</button>
    </div>
  );
};
export default App;

你可能感兴趣的:(React,react.js,javascript,前端)