Как передать данные от одного компонента к другому?

В React и React Native передача данных между компонентами — ключевая часть архитектуры приложения. React использует односторонний поток данных (unidirectional data flow), что означает: данные передаются от родителя к потомку через props. Это делает управление состоянием предсказуемым и простым для отладки. Тем не менее, существует несколько способов передать данные между компонентами в разных ситуациях — от простых до более сложных.

1. Передача данных от родительского компонента к дочернему с помощью props

Это самый прямой и основной способ передачи данных.

Пример:

function Parent() {
const userName = 'Алиса';
return <Child name={userName} />;
}
function Child({ name }) {
return <Text>Привет, {name}</Text>;
}
  • Parent передаёт строку 'Алиса' в Child через props.

  • Child получает её как name.

Можно передавать любые типы данных: строки, числа, объекты, массивы, функции и даже JSX-компоненты.

2. Передача функции от родителя к дочернему компоненту

Если нужно сообщить родителю о действии в дочернем компоненте, родитель может передать функцию как проп.

Пример:

function Parent() {
const handleSelect = (value) => {
console.log('Выбрано:', value);
};
return <Child onSelect={handleSelect} />;
}
function Child({ onSelect }) {
return (
<Button title="Нажми" onPress={() => onSelect('React Native')} />
);
}
  • Parent передаёт функцию handleSelect в Child.

  • Child вызывает эту функцию с аргументом, который передаётся родителю.

Этот способ называется "подъём состояния" (state lifting), когда логика управления данными находится в родителе, а действия инициируются из потомков.

3. Передача данных от дочернего компонента к родительскому (обратная связь)

React не позволяет напрямую передавать данные от дочернего к родителю. Но это решается тем, что родитель передаёт функцию-обработчик, как было выше.

Пример с формой:

function Parent() {
const \[email, setEmail\] = useState('');
return <EmailInput onChange={setEmail} />;
}
function EmailInput({ onChange }) {
return (
<TextInput
placeholder="Введите email"
onChangeText={(text) => onChange(text)}
/>
);
}
  • Родитель передаёт setEmail в EmailInput.

  • EmailInput вызывает onChange, передавая текст обратно родителю.

4. Передача данных между компонентами, не связанными напрямую

Когда два компонента не находятся в отношениях родитель-потомок, передача данных становится более сложной. Возможные решения:

A) Контекст (React Context API)

Контекст позволяет создать глобальное хранилище данных, к которому могут обращаться любые компоненты внутри дерева компонентов, минуя промежуточные слои.

Создание контекста:

const UserContext = React.createContext();

Родитель предоставляет данные:

function App() {
const user = { name: 'Алиса', role: 'админ' };
return (
&lt;UserContext.Provider value={user}&gt;
&lt;Profile /&gt;
&lt;/UserContext.Provider&gt;
);
}

Потомок получает данные:

function Profile() {
const user = useContext(UserContext);
return &lt;Text&gt;Пользователь: {user.name}&lt;/Text&gt;;
}

Контекст идеален для:

  • Текущего пользователя

  • Тем оформления

  • Языков (i18n)

  • Настроек приложения

B) Навигационные параметры (React Navigation)

Если вы переходите с одного экрана на другой, можно передать данные через параметры навигации.

Передача:

navigation.navigate('Profile', { userId: 123 });

Получение:

function ProfileScreen({ route }) {
const { userId } = route.params;
return &lt;Text&gt;ID пользователя: {userId}&lt;/Text&gt;;
}

Или через хук:

import { useRoute } from '@react-navigation/native';
const route = useRoute();
const { userId } = route.params;

Навигационные параметры удобны для одноразовой передачи данных между экранами (в отличие от глобального состояния).

C) Глобальное состояние (Redux, Zustand, Jotai, Recoil и др.)

Для сложных приложений, где данные изменяются во многих местах, используется централизованное хранилище состояний, например:

Пример с Redux:

// store.js
const userSlice = createSlice({
name: 'user',
initialState: { name: '', role: '' },
reducers: {
setUser: (state, action) => {
state.name = action.payload.name;
state.role = action.payload.role;
}
}
});

Использование в любом компоненте:

const user = useSelector(state => state.user);
const dispatch = useDispatch();
dispatch(setUser({ name: 'Алиса', role: 'admin' }));

Глобальные хранилища позволяют синхронизировать данные между экранами, списками, формами и другими элементами UI, не связывая их напрямую через props.

5. Использование useImperativeHandle и forwardRef

Когда родитель хочет управлять дочерним компонентом напрямую, можно использовать ссылки (refs).

Пример:

const CustomInput = React.forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focusInput: () => inputRef.current.focus(),
}));
return &lt;TextInput ref={inputRef} /&gt;;
});
function Parent() {
const ref = useRef();
return (
<>
&lt;CustomInput ref={ref} /&gt;
&lt;Button title="Фокус" onPress={() =&gt; ref.current.focusInput()} />
&lt;/&gt;
);
}

Этот подход используется редко, в основном для управления поведением дочерних компонентов, таких как TextInput, Modal, ScrollView и т.д.

6. Использование событий (через EventEmitter)

В сложных сценариях (например, когда компонент находится вне дерева NavigationContainer) можно использовать шину событий:

import { EventEmitter } from 'events';
const emitter = new EventEmitter();
function Sender() {
return (
<Button
title="Отправить"
onPress={() => emitter.emit('myEvent', { message: 'Привет' })}
/>
);
}
function Receiver() {
useEffect(() => {
emitter.on('myEvent', (data) => {
console.log(data.message);
});
return () => emitter.removeAllListeners('myEvent');
}, \[\]);
return &lt;Text&gt;Готов к получению&lt;/Text&gt;;
}

Однако EventEmitter не рекомендуется как основной способ передачи данных. Он может быть полезен в плагинах или библиотеках, но сложен для отладки и масштабирования.

7. Локальное хранилище и асинхронные источники

Если компоненты сильно изолированы и работают в разное время, можно сохранять данные во внешние хранилища:

  • AsyncStorage — для постоянного хранения

  • SecureStore — для защищённого хранения

  • SQLite или Realm — для локальной базы данных

  • fetch()/axios — для передачи данных через сервер

Пример:

import AsyncStorage from '@react-native-async-storage/async-storage';
async function saveUser(user) {
await AsyncStorage.setItem('user', JSON.stringify(user));
}
async function loadUser() {
const json = await AsyncStorage.getItem('user');
return JSON.parse(json);
}

В React Native существует гибкая система передачи данных, начиная от props и заканчивая глобальными сторами и контекстами. Выбор подхода зависит от конкретной задачи: связаны ли компоненты напрямую, нужно ли отслеживать изменения, как долго должны храниться данные и т.д.