ReactJS - getSnapshotBeforeUpdate() 方法



正如我们所知,在 React 中,每个组件都有自己的生命周期,这意味着它们在我们的项目中运行时会经历不同的阶段。React 提供了用于控制这些进程的内置方法。

因此,现在让我们看一下 getSnapshotBeforeUpdate() 方法。想象一下,我们正在使用 React 和一个定期接收消息的聊天组件创建一个网页。现在,我们不希望每次有新消息进来时滚动位置都会改变。允许用户在对话中失去自己的位置。这就是 getSnapshotBeforeUpdate 的用武之地。

简单来说,React 会在对网页进行修改之前不久调用这个函数。它允许我们的组件在任何可能的更改发生之前从页面捕获一些信息,例如用户滚动到的位置。

语法


getSnapshotBeforeUpdate(prevProps, prevState)

参数

  • prevProps - 这些是更改之前存在的属性。可以将它们与 this.props 进行比较,看看有什么新变化。
  • prevState - 这是更改前的上一个状态。若要确定更改,请将其与 this.state 进行比较。

返回值

我们应该返回任何类型的快照值或 null。我们返回的值将作为第三个参数发送到 componentDidUpdate。

例子

示例 1

让我们构建一个使用 getSnapshotBeforeUpdate 函数的小型 React 应用程序。在这个例子中,我们将构建一个简单的聊天应用程序,其中有新的消息,我们希望保存滚动位置。


import React, { Component } from 'react';

class App extends Component {
	 	constructor(props) {
	 	 	 super(props);
	 	 	 this.state = {
	 	 	 	 	messages: [
	 	 	 	 	 	 { id: 1, text: 'Hello!' },
	 	 	 	 	 	 { id: 2, text: 'How are you?' },
	 	 	 	 	],
	 	 	 	 	newMessage: '',
	 	 	 };
	 	
	 	 	 this.chatWindowRef = React.createRef();
	 	} 		
	 	handleInputChange = (event) => {
	 	 	 this.setState({ newMessage: event.target.value });
	 	}; 		
	 	handleSendMessage = () => {
	 	 	 const { messages, newMessage } = this.state;
	 	 		
	 	 	 // Create a new message object
	 	 	 const newMessageObj = {
	 	 	 	 	id: messages.length + 1,
	 	 	 	 	text: newMessage,
	 	 	 };
	 	 		
	 	 	 // Update the state with the new message
	 	 	 this.setState({
	 	 	 	 	messages: [...messages, newMessageObj],
	 	 	 	 	newMessage: '',
	 	 	 });
	 	}; 		
	 	getSnapshotBeforeUpdate(prevProps, prevState) {
	 	 	 // Check if new messages are being added
	 	 	 if (prevState.messages.length < this.state.messages.length) {
	 	 	 	 	const chatWindow = this.chatWindowRef.current;
	 	 	 	 	return chatWindow.scrollHeight - chatWindow.scrollTop;
	 	 	 }
	 	 	 return null;
	 	} 		
	 	componentDidUpdate(prevProps, prevState, snapshot) {
	 	 	 // If there's a snapshot, adjust the scroll position
	 	 	 if (snapshot !== null) {
	 	 	 	 	const chatWindow = this.chatWindowRef.current;
	 	 	 	 	chatWindow.scrollTop = chatWindow.scrollHeight - snapshot;
	 	 	 }
	 	} 		
	 	render() {
	 	 	 const { messages, newMessage } = this.state; 	 	 	
	 	 	 return (
	 	 	 	 	<div>
	 	 	 	 	 	 <div
	 	 	 	 	 	 	 	ref={this.chatWindowRef}
	 	 	 	 	 	 	 	style={{ height: '200px', overflowY: 'scroll', border: '1px solid #ccc', padding: '10px' }}
	 	 	 	 	 	 	 	>
	 	 	 	 	 	 	 	{/* Display messages */}
	 	 	 	 	 	 	 	{messages.map((message) => (
	 	 	 	 	 	 	 	 	 <div key={message.id}>{message.text}</div>
	 	 	 	 	 	 	 	))}
	 	 	 	 	 	 </div>
	 	 	 	 	 	 	 	
	 	 	 	 	 	 	 	{/* Input for new message */}
	 	 	 	 	 	 	 	<div>
	 	 	 	 	 	 	 	 	 <input type="text" value={newMessage} onChange={this.handleInputChange} />
	 	 	 	 	 	 	 	 	 <button onClick={this.handleSendMessage}>Send Message</button>
	 	 	 	 	 	 </div>
	 	 	 	 	</div>
	 	 	 );
	 	}
}

export default App;

输出

发送短消息

在这个应用程序中 -

  • ChatApp 组件保留一个消息列表和一个用于添加新消息的表单。
  • 函数 getSnapshotBeforeUpdate 用于确定是否正在添加新消息,并记录当前滚动位置。
  • 如果添加了新消息,componentDidUpdate 将更新滚动位置。
  • 聊天窗口包括一个用于显示消息的可滚动区域。

示例 2

让我们创建一个简单的 React 应用程序,用户可以在其中输入数字并执行基本的算术运算。这是代码 -


import React, { Component } from 'react';
import './App.css';

class CalculatorApp extends Component {
	 	constructor(props) {
	 	 	 super(props);
	 	 	 this.state = {
	 	 	 	 	result: 0,
	 	 	 	 	num1: '',
	 	 	 	 	num2: '',
	 	 	 	 	operator: '+',
	 	 	 };
	 	} 		
	 	handleNumChange = (event, numType) => {
	 	 	 const value = event.target.value;
	 	
	 	 	 this.setState({
	 	 	 	 	[numType]: value,
	 	 	 });
	 	}; 		
	 	handleOperatorChange = (event) => {
	 	 	 this.setState({
	 	 	 	 	operator: event.target.value,
	 	 	 });
	 	}; 		
	 	handleCalculate = () => {
	 	 	 const { num1, num2, operator } = this.state;
	 	 		
	 	 	 // Convert input values to numbers
	 	 	 const number1 = parseFloat(num1);
	 	 	 const number2 = parseFloat(num2);
	 	 		
	 	 	 // Perform calculation based on the selected operator
	 	 	 let result = 0;
	 	 	 switch (operator) {
	 	 	 	 	case '+':
	 	 	 	 	 	 result = number1 + number2;
	 	 	 	 	break;
	 	 	 	 	case '-':
	 	 	 	 	 	 result = number1 - number2;
	 	 	 	 	break;
	 	 	 	 	case '*':
	 	 	 	 	 	 result = number1 * number2;
	 	 	 	 	break;
	 	 	 	 	case '/':
	 	 	 	 	 	 result = number1 / number2;
	 	 	 	 	break;
	 	 	 	 	default:
	 	 	 	 	break;
	 	 	 }
	 	 		
	 	 	 // Update the state with result
	 	 	 this.setState({
	 	 	 	 	result,
	 	 	 });
	 	};
	 	
	 	render() {
	 	 	 const { result, num1, num2, operator } = this.state; 	 	 	
	 	 	 return (
	 	 	 	 	<div className='App'>
	 	 	 	 	 	 <div>
	 	 	 	 	 	 	 	<input type="number" value={num1} onChange={(e) => this.handleNumChange(e, 'num1')} />
	 	 	 	 	 	 	 	<select value={operator} onChange={this.handleOperatorChange}>
	 	 	 	 	 	 	 	 	 <option value="+">+</option>
	 	 	 	 	 	 	 	 	 <option value="-">-</option>
	 	 	 	 	 	 	 	 	 <option value="*">*</option>
	 	 	 	 	 	 	 	 	 <option value="/">/</option>
	 	 	 	 	 	 	 	</select>
	 	 	 	 	 	 	 	<input type="number" value={num2} onChange={(e) => this.handleNumChange(e, 'num2')} />
	 	 	 	 	 	 	 	<button onClick={this.handleCalculate}>Calculate</button>
	 	 	 	 	 	 </div>
	 	 	 	 	 	 <div>
	 	 	 	 	 	 	 	<strong>Result:</strong> {result}
	 	 	 	 	 	 </div>
	 	 	 	 	</div>
	 	 	 );
	 	}
}

export default CalculatorApp;

输出

计算结果

在此代码中,我们创建了一个简单的计算器应用程序,用户可以在其中输入两个数字,选择一个算术运算符,并在单击“计算”按钮后查看结果。

示例 3

让我们创建一个小型的 React 应用程序,让用户输入任务并将其标记为已完成。当添加新任务时,我们将使用 getSnapshotBeforeUpdate 函数滚动到任务列表的底部。这是该应用程序的代码 -


import React, { Component } from 'react';

class TaskListApp extends Component {
	 	constructor(props) {
	 	 	 super(props);
	 	 	 this.state = {
	 	 	 	 	tasks: [],
	 	 	 	 	newTask: '',
	 	 	 };
	 	 		
	 	 	 this.taskListRef = React.createRef();
	 	} 		
	 	handleInputChange = (event) => {
	 	 	 this.setState({ newTask: event.target.value });
	 	}; 		
	 	handleAddTask = () => {
	 	 	 const { tasks, newTask } = this.state;
	 	 		
	 	 	 // Create a new task object
	 	 	 const newTaskObj = {
	 	 	 	 	id: tasks.length + 1,
	 	 	 	 	text: newTask,
	 	 	 	 	completed: false,
	 	 	 };
	 	 		
	 	 	 // Update the state with the new task
	 	 	 this.setState({
	 	 	 	 	tasks: [...tasks, newTaskObj],
	 	 	 	 	newTask: '',
	 	 	 });
	 	};
	 	
	 	getSnapshotBeforeUpdate(prevProps, prevState) {
	 	 	 // Check if new tasks are being added
	 	 	 if (prevState.tasks.length < this.state.tasks.length) {
	 	 	 	 	const taskList = this.taskListRef.current;
	 	 	 	 	return taskList.scrollHeight - taskList.scrollTop;
	 	 	 }
	 	 	 return null;
	 	} 		
	 	componentDidUpdate(prevProps, prevState, snapshot) {
	 	 	 if (snapshot !== null) {
	 	 	 	 	const taskList = this.taskListRef.current;
	 	 	 taskList.scrollTop = taskList.scrollHeight - snapshot;
	 	 	 }
	 	} 		
	 	handleToggleComplete = (taskId) => {
	 	 	 const updatedTasks = this.state.tasks.map((task) =>
	 	 	 	 	task.id === taskId ? { ...task, completed: !task.completed } : task
	 	 	 );
	 	 		
	 	 	 this.setState({
	 	 	 	 	tasks: updatedTasks,
	 	 	 });
	 	}; 		
	 	render() {
	 	 	 const { tasks, newTask } = this.state; 	 	 	
	 	 	 return (
	 	 	 	 	<div>
	 	 	 	 	 	 <div
	 	 	 	 	 	 	 	ref={this.taskListRef}
	 	 	 	 	 	 	 	style={{ height: '200px', overflowY: 'scroll', border: '1px solid #ccc', padding: '10px' }}
	 	 	 	 	 	 >
	 	 	 	 	 	 	 	{/* Display tasks */}
	 	 	 	 	 	 	 	{tasks.map((task) => (
	 	 	 	 	 	 	 	 	 <div key={task.id} style={{ textDecoration: task.completed ? 'line-through' : 'none' }}>
	 	 	 	 	 	 	 	 	 <input
	 	 	 	 	 	 	 	 	 	 	type="checkbox"
	 	 	 	 	 	 	 	 	 	 	checked={task.completed}
	 	 	 	 	 	 	 	 	 	 	onChange={() => this.handleToggleComplete(task.id)}
	 	 	 	 	 	 	 	 	 />
	 	 	 	 	 	 	 	 	 {task.text}
	 	 	 	 	 	 	 	 	 </div>
	 	 	 	 	 	 	 	))}
	 	 	 	 	 	 </div>
	 	 	 	 	 		
	 	 	 	 	 	 {/* Input for new task */}
	 	 	 	 	 	 <div>
	 	 	 	 	 	 	 	<input type="text" value={newTask} onChange={this.handleInputChange} />
	 	 	 	 	 	 	 	<button onClick={this.handleAddTask}>Add Task</button>
	 	 	 	 	 	 </div>
	 	 	 	 	</div>
	 	 	 );
	 	}
}

export default TaskListApp;

输出

添加任务

在此应用程序中,用户可以输入任务,将其标记为已完成,并在列表中查看任务。添加新任务时,将使用函数 getSnapshotBeforeUpdate 滚动到任务列表的底部。

笔记

  • 如果定义了 shouldComponentUpdate 并返回 false,则 React 将不会调用 getSnapshotBeforeUpdate。
  • 目前,对于函数组件,没有直接等同于 getSnapshotBeforeUpdate。如果我们需要此功能,则必须使用类组件。

总结

因此,我们已经看到了 getSnapshotBeforeUpdate() 函数的工作机制。此外,我们还创建了一个小应用程序来展示该功能的使用情况。这个组件可以包含在我们的 React 应用程序中,以展示 getSnapshotBeforeUpdate 如何在添加新内容时用于保持滚动位置。