function ProfilePage(props) { // 堆代码 duidaima.com const showMessage = () => alert('你好 ' + props.user); const handleClick = () => setTimeout(showMessage, 3000); return <button onClick={handleClick}>Follow</button> }上述组件:如果 props.user是 Dan,它会在三秒后显示 你好 Dan。如果是类组件我们怎么写?一个简单的重构可能就象这样:
class ProfilePage extends React.Component { showMessage = () => alert('Followed ' + this.props.user); handleClick = () => setTimeout(this.showMessage, 3000); render() { return <button onClick={this.handleClick}>Follow</button>; } }通常我们认为,这两个代码片段是等效的。人们经常在这两种模式中自由的重构代码,但是很少注意到它们的含义:
3.查看 弹出的文本。
showMessage = () => { alert('Followed ' + this.props.user); };这个类方法从 this.props.user 中读取数据。在 React 中 Props 是 不可变(immutable)的,所以他们永远不会改变。而 this 是而且永远是 可变(mutable)的。**
class ProfilePage extends React.Component { showMessage = (user) => alert('Followed ' + user); handleClick = () => { const {user} = this.props; setTimeout(() => this.showMessage(user), 3000); }; render() { return <button onClick={this.handleClick}>Followbutton>; } }然而,这种方法使得代码明显变得更加冗长。如果我们需要的不止是一个props 该怎么办?如果我们还需要访问state 又该怎么办?如果 showMessage 调用了另一个方法,然后那个方法中读取了 this.props.something 或者 this.state.something ,我们又将遇到同样的问题。然后我们不得不将 this.props和 this.state以函数参数的形式在被 showMessage调用的每个方法中一路传递下去。
class ProfilePage extends React.Component { render() { const props = this.props; const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return <button onClick={handleClick}>Follow</button>; } }你在渲染的时候就已经"捕获"了props:。这样,在它内部的任何代码(包括 showMessage)都保证可以得到这一次特定渲染所使用的props。
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }就像上面这样, props仍旧被捕获了 —— React将它们作为参数传递。不同于 this , props 对象本身永远不会被React改变。当父组件使用不同的props来渲染 ProfilePage时,React会再次调用 ProfilePage函数。但是我们点击的事件处理函数,"属于"具有自己的 user值的上一次渲染,并且 showMessage回调函数也能读取到这个值。它们都保持完好无损。
function MessageThread() { const [message, setMessage] = useState(''); const showMessage = () => { alert('You said: ' + message); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); }; return <> <input value={message} onChange={handleMessageChange} /> <button onClick={handleSendClick}>Send</button> </>; }如果我发送一条特定的消息,组件不应该对实际发送的是哪条消息感到困惑。这个函数组件的 message变量捕获了"属于"返回了被浏览器调用的单击处理函数的那一次渲染。所以当我点击"发送"时 message被设置为那一刻在input中输入的内容。
function MyComponent() { const ref = useRef(null); }但是,你必须自己管理它。一个ref与一个实例字段扮演同样的角色。这是进入可变的命令式的世界的后门。你可能熟悉'DOM refs',但是ref在概念上更为广泛通用。它只是一个你可以放东西进去的盒子。
function MessageThread() { const [message, setMessage] = useState(''); const latestMessage = useRef(''); const showMessage = () => { alert('You said: ' + latestMessage.current); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); latestMessage.current = e.target.value; };如果我们在 showMessage中读取 message,我们将得到在我们按下发送按钮那一刻的信息。但是当我们读取 latestMessage.current,我们将得到最新的值 —— 即使我们在按下发送按钮后继续输入。
function MessageThread() { const [message, setMessage] = useState(''); const latestMessage = useRef(''); useEffect(() => { latestMessage.current = message; }); const showMessage = () => { alert('You said: ' + latestMessage.current); };我们在一个effect 内部执行赋值操作以便让ref的值只会在DOM被更新后才会改变。这确保了我们的变量突变不会破坏依赖于可中断渲染的时间切片和 Suspense 等特性。通常来说使用这样的ref并不是非常地必要。捕获props和state通常是更好的默认值。 然而,在处理类似于intervals和订阅这样的命令式API时,ref会十分便利。你可以像这样跟踪 任何值 —— 一个prop,一个state变量,整个props对象,或者甚至一个函数。