0Pricing
React Native Academy · 课时

使用 props 传递数据

定义一个接受 props 的可复用组件,从父组件传递值,并使用 PropTypes 或 TypeScript 说明预期的 prop 结构。

使用 props 传递数据 是 CoddyKit 上的免费 React Native Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Native Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Native Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

What Are Props?

Props (short for properties) are the mechanism React components use to receive input from their parent. A parent component renders a child component and passes data to it through attributes in JSX — just like HTML attributes, but for any JavaScript value including strings, numbers, objects, arrays, and functions. The child component receives all its props as a single object argument. Props flow in one direction: from parent down to child. A child component can never modify its own props — they are read-only.

import { Text, View } from 'react-native';

// Child component receives props
function Greeting({ name, age }) {
  return (
    <View>
      <Text>Hello, {name}!</Text>
      <Text>You are {age} years old.</Text>
    </View>
  );
}

// Parent passes data through props
export default function App() {
  return (
    <View style={{ flex: 1, padding: 24 }}>
      <Greeting name='Alice' age={30} />
      <Greeting name='Bob' age={25} />
    </View>
  );
}

Destructuring Props

You can access props in two ways: as a single props object (function Card(props) { props.title }) or by destructuring directly in the function signature (function Card({ title, subtitle })). Destructuring is the preferred style because it makes the accepted props explicit and the code more concise. You can also provide default values for props in the destructuring syntax: { size = 16, color = '#333' }. This means the prop is optional and falls back to the default when not provided by the parent.

import { Text, View, StyleSheet } from 'react-native';

// Destructuring with default values
function Badge({ label, color = '#4f86f7', size = 14 }) {
  return (
    <View style={[styles.badge, { backgroundColor: color }]}>
      <Text style={[styles.text, { fontSize: size }]}>{label}</Text>
    </View>
  );
}

export default function App() {
  return (
    <View style={{ gap: 8, padding: 16 }}>
      <Badge label='New' />                        {/* uses defaults */}
      <Badge label='Sale' color='#e74c3c' />       {/* custom color */}
      <Badge label='Pro' color='#2ecc71' size={12} />
    </View>
  );
}

const styles = StyleSheet.create({
  badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, alignSelf: 'flex-start' },
  text: { color: '#fff', fontWeight: 'bold' },
});

Passing Different Prop Types

Props can carry any JavaScript value. Strings don't need curly braces (title='Hello'), but all other types do — numbers (count={5}), booleans (disabled={true} or just disabled), objects (style={{ color: 'red' }}), arrays (items={[1,2,3]}), and functions (onPress={handlePress}). Passing functions as props is how children communicate back to parents — the parent defines the function and passes it down; the child calls it when something happens. This pattern is central to React's unidirectional data flow.

import { View, Text, TouchableOpacity } from 'react-native';

// onPress is a function prop — callback from child to parent
function ConfirmButton({ label, count, disabled, onPress }) {
  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={disabled}
      style={{ opacity: disabled ? 0.4 : 1, backgroundColor: '#4f86f7', padding: 12, borderRadius: 8 }}
    >
      <Text style={{ color: '#fff', fontWeight: 'bold' }}>
        {label} ({count})
      </Text>
    </TouchableOpacity>
  );
}

export default function App() {
  return (
    <View style={{ padding: 24 }}>
      <ConfirmButton
        label='Add to Cart'
        count={3}
        disabled={false}
        onPress={() => console.log('Added!')}
      />
    </View>
  );
}

The children Prop

The special children prop holds whatever you put between the opening and closing tags of a component. This enables composition — you can build wrapper or container components that apply layout, styling, or behavior to any content nested inside. React Native's View, ScrollView, and TouchableOpacity all work this way. Define your own wrapper components with children to create reusable layout shells like cards, modals, and screen templates.

import { View, StyleSheet } from 'react-native';

// A Card wrapper that applies consistent styling
function Card({ children, style }) {
  return (
    <View style={[styles.card, style]}>
      {children}
    </View>
  );
}

export default function App() {
  return (
    <View style={{ padding: 16 }}>
      <Card>
        {/* Any content can go inside the Card */}
        <Text>Title</Text>
        <Text>Subtitle</Text>
        <Image source={{ uri: 'https://picsum.photos/200' }} style={{ width: 200, height: 100 }} />
      </Card>
    </View>
  );
}

const styles = StyleSheet.create({
  card: { backgroundColor: '#fff', borderRadius: 16, padding: 16, elevation: 3, marginBottom: 12 },
});

Prop Spreading with the Spread Operator

Use the JavaScript spread operator to pass all properties of an object as individual props. This is convenient when you have a data object with many fields: <UserCard {...user} /> is equivalent to <UserCard id={user.id} name={user.name} avatar={user.avatar} />. The spread pattern is common when rendering items from an array with .map(). Be careful not to spread unknown props onto native components (View, Text) — they will trigger warnings or be silently ignored. Only spread props onto custom components that you control.

import { View, Text } from 'react-native';

const users = [
  { id: '1', name: 'Alice', role: 'Admin' },
  { id: '2', name: 'Bob', role: 'User' },
];

function UserRow({ name, role }) {
  return (
    <View style={{ padding: 12, borderBottomWidth: 1, borderBottomColor: '#eee' }}>
      <Text style={{ fontWeight: 'bold' }}>{name}</Text>
      <Text style={{ color: '#666' }}>{role}</Text>
    </View>
  );
}

export default function UserList() {
  return (
    <View>
      {users.map(user => (
        <UserRow key={user.id} {...user} />
      ))}
    </View>
  );
}

TypeScript Props with Interfaces

In TypeScript, define the shape of your props using an interface or type. This catches errors at compile time — passing a number where a string is expected, or forgetting a required prop, shows an error before you even run the app. Mark optional props with ?. TypeScript also enables editor auto-complete: when you type <MyComponent in JSX, your editor lists all available props and their types. This is one of the biggest productivity benefits of using TypeScript in React Native projects.

import { View, Text, StyleSheet } from 'react-native';

interface ProductCardProps {
  id: string;
  name: string;
  price: number;
  imageUri: string;
  badge?: string;      // optional
  onPress?: () => void; // optional callback
}

export default function ProductCard({
  name,
  price,
  imageUri,
  badge,
  onPress,
}: ProductCardProps) {
  return (
    <View style={styles.card}>
      <Text style={styles.name}>{name}</Text>
      <Text style={styles.price}>${price.toFixed(2)}</Text>
      {badge && <Text style={styles.badge}>{badge}</Text>}
    </View>
  );
}

const styles = StyleSheet.create({
  card: { padding: 16, backgroundColor: '#fff', borderRadius: 12 },
  name: { fontSize: 16, fontWeight: '600' },
  price: { fontSize: 18, color: '#4f86f7', fontWeight: 'bold', marginTop: 4 },
  badge: { color: '#e74c3c', fontSize: 12, marginTop: 4 },
});

Callback Props: Child to Parent Communication

Since data flows only downward in React, a child communicates back to its parent by calling a callback function passed as a prop. The parent defines the function (what should happen when the user acts), passes it to the child, and the child calls it at the right time. This is the standard pattern for buttons, form inputs, and list items — the child component doesn't care about what happens next (no business logic), it just calls the prop function with any relevant data.

import { View, Text, TouchableOpacity } from 'react-native';

// Child: knows nothing about business logic
function DeleteButton({ itemId, onDelete }) {
  return (
    <TouchableOpacity onPress={() => onDelete(itemId)}>
      <Text style={{ color: 'red' }}>Delete</Text>
    </TouchableOpacity>
  );
}

// Parent: owns the logic and state
export default function ItemList() {
  const [items, setItems] = React.useState(['Apple', 'Banana', 'Cherry']);

  function handleDelete(index) {
    setItems(prev => prev.filter((_, i) => i !== index));
  }

  return (
    <View>
      {items.map((item, i) => (
        <View key={i} style={{ flexDirection: 'row', justifyContent: 'space-between', padding: 12 }}>
          <Text>{item}</Text>
          <DeleteButton itemId={i} onDelete={handleDelete} />
        </View>
      ))}
    </View>
  );
}

Avoiding Prop Drilling

Prop drilling happens when you pass props through several intermediate components that don't need the data themselves — they just forward it deeper. For example, passing a user object from App → Screen → Section → Card → Avatar just to display the avatar image. This becomes difficult to maintain as the hierarchy grows. The solution is to use Context (for global or shared data like the logged-in user or theme) or to colocate state closer to the components that need it. Don't over-engineer early — a few levels of prop passing is perfectly fine.

// Prop drilling anti-pattern (not ideal when deep)
function App() {
  const user = { name: 'Alice', avatar: 'https://...' };
  return <Screen user={user} />;
}
function Screen({ user }) {
  return <Section user={user} />; // just passing through
}
function Section({ user }) {
  return <Avatar user={user} />; // just passing through
}
function Avatar({ user }) {
  return <Image source={{ uri: user.avatar }} style={{ width: 40, height: 40 }} />; // actually uses it
}

// Solution: Context (covered later)
// Or: pass only the specific value needed at each level
function Section({ avatarUri }) { ... }
function Avatar({ uri }) { ... }

Immutable Props: Why Children Cannot Modify Them

A core rule of React: a component must never modify its own props. Props are the parent's data passed to the child for display — they are owned by the parent. If a child needs to change a value, it should call a callback prop to tell the parent, and the parent updates its state. The parent then passes the updated value back down as a new prop. This unidirectional flow makes data changes predictable and debuggable — you always know where state lives and who controls it.

// WRONG: Never mutate props directly
function Counter({ count }) {
  // This is an error — do not do this:
  // count = count + 1;

  return <Text>{count}</Text>;
}

// CORRECT: Notify parent via callback, parent updates state
function Counter({ count, onIncrement }) {
  return (
    <TouchableOpacity onPress={onIncrement}>
      <Text>{count}</Text>
    </TouchableOpacity>
  );
}

// In the parent:
const [count, setCount] = useState(0);
<Counter count={count} onIncrement={() => setCount(c => c + 1)} />

Real-World Props Example: Feed Post

Apply all prop concepts to a realistic social media feed post component. The component receives data from the feed list (text, image, author, like count, timestamp) and callback functions for user interactions (like, comment, share). The component is purely presentational — all data comes in through props, all interactions flow out through callback props. This design makes the FeedPost component easy to test in isolation and reuse across different feed types (home feed, profile feed, search results).

import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';

export default function FeedPost({
  author, avatarUri, imageUri, caption, likes, timeAgo,
  onLike, onComment, onShare
}) {
  return (
    <View style={styles.post}>
      <View style={styles.header}>
        <Image source={{ uri: avatarUri }} style={styles.avatar} />
        <View>
          <Text style={styles.author}>{author}</Text>
          <Text style={styles.time}>{timeAgo}</Text>
        </View>
      </View>
      <Image source={{ uri: imageUri }} style={styles.postImage} />
      <Text style={styles.caption}>{caption}</Text>
      <View style={styles.actions}>
        <TouchableOpacity onPress={onLike}><Text>❤️ {likes}</Text></TouchableOpacity>
        <TouchableOpacity onPress={onComment}><Text>💬 Comment</Text></TouchableOpacity>
        <TouchableOpacity onPress={onShare}><Text>🔗 Share</Text></TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  post: { backgroundColor: '#fff', marginBottom: 8 },
  header: { flexDirection: 'row', alignItems: 'center', padding: 12, gap: 10 },
  avatar: { width: 40, height: 40, borderRadius: 20 },
  author: { fontWeight: 'bold', fontSize: 14 },
  time: { fontSize: 12, color: '#999' },
  postImage: { width: '100%', aspectRatio: 1 },
  caption: { padding: 12, fontSize: 14 },
  actions: { flexDirection: 'row', gap: 20, padding: 12, borderTopWidth: 1, borderTopColor: '#eee' },
});

Default Props with defaultProps

In addition to destructuring defaults, React components can define defaultProps as a static property — a plain object that specifies fallback values for each optional prop. When a prop is undefined (not passed by the parent), React substitutes the value from defaultProps. This pattern is older than destructuring defaults and works the same way, but is less common in modern TypeScript codebases where interface defaults are preferred. Both approaches achieve the same result: making optional props safe to consume without null checks.

import { Text, View, StyleSheet } from 'react-native';

function Avatar({ size, color, initials }) {
  return (
    <View style={[styles.circle, { width: size, height: size, borderRadius: size / 2, backgroundColor: color }]}>
      <Text style={styles.initials}>{initials}</Text>
    </View>
  );
}

// defaultProps: applied when prop is undefined
Avatar.defaultProps = {
  size: 48,
  color: '#4f86f7',
};

// Usage:
<Avatar initials='AB' />                    // uses defaults: 48px blue
<Avatar initials='CD' size={64} />          // custom size, default color
<Avatar initials='EF' size={32} color='#e74c3c' />  // all custom

const styles = StyleSheet.create({
  circle: { alignItems: 'center', justifyContent: 'center' },
  initials: { color: '#fff', fontWeight: 'bold', fontSize: 16 },
});

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: props are read-only values passed from parent to child in one direction, callback function props allow children to notify parents of user actions, and TypeScript interfaces document and enforce the expected prop shape at compile time. Next up we explore managing changing values inside components with the useState hook.

常见问题解答

「使用 props 传递数据」课时是免费的吗?

是的 — 「使用 props 传递数据」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Native Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Native Academy 课程共包含 4 节课。

「使用 props 传递数据」这节课中我会学到什么?

定义一个接受 props 的可复用组件,从父组件传递值,并使用 PropTypes 或 TypeScript 说明预期的 prop 结构。 你通过在浏览器中直接运行的动手代码来练习 React Native Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 React Native Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 React Native Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「使用 props 传递数据」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 React Native Academy 课中编写并运行代码吗?

能。每节 React Native Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 props 传递数据
  2. 使用 useState 管理组件状态
  3. 提升状态
  4. 构建交互式计数器应用
← 返回 React Native Academy