从 iOS 到 Web:React 基础概念指南

image.png

从 iOS 到 Web:React 基础概念指南

写给 iOS 开发者的 React 入门教程

作为一名 iOS 开发者转向 Web 开发,你会发现很多概念有相似之处,但也有本质区别。本文将通过对比 iOS (Objective-C) 和 React 的方式,帮助你快速理解 React 的核心概念。

目录


一、Props:外部传入的数据

什么是 Props?

Props(properties 的缩写)是父组件传递给子组件的数据,类似于 iOS 中的初始化参数或依赖注入。

iOS vs React 对比

iOS (Objective-C):


// DetailViewController.h

@interface DetailViewController : UIViewController

  

@property (nonatomic, copy) NSString *userId;

@property (nonatomic, copy) NSString *userName;

- (instancetype)initWithUserId:(NSString *)userId

userName:(NSString *)userName;

@end

  

// DetailViewController.m

@implementation DetailViewController

  

- (instancetype)initWithUserId:(NSString *)userId

userName:(NSString *)userName {

if (self = [super init]) {

_userId = userId;

_userName = userName;

}

return self;

}

  

@end

  

// 使用

DetailViewController *vc = [[DetailViewController alloc]

initWithUserId:@"123"

userName:@"张三"];

React:


// 父组件传入 props

class ParentComponent extends React.Component {

render() {

return (

<DetailComponent

userId="123" // 传入 userId

userName="张三" // 传入 userName

/>

);

}

}

  

// 子组件接收 props

class DetailComponent extends React.Component {

render() {

// 通过 this.props 访问

const { userId, userName } = this.props;

  

return (

<div>

<p>用户ID: {userId}</p>

<p>用户名: {userName}</p>

</div>

);

}

}

Props 的特点

1. 只读性:子组件不能修改 props


class ChildComponent extends React.Component {

render() {

// ✅ 可以读取

console.log(this.props.name);

  

// ❌ 不能修改(会报错或无效)

this.props.name = '新名字'; // 错误!

  

return <div>{this.props.name}</div>;

}

}

2. 动态性:父组件改变 props,子组件会自动重新渲染


class Parent extends React.Component {

state = { count: 0 };

  

render() {

return (

<div>

<button onClick={() => this.setState({ count: this.state.count + 1 })}>

增加

</button>

{/* count 改变,Child 会自动重新渲染 */}

<Child count={this.state.count} />

</div>

);

}

}

3. 无需声明:JavaScript 动态特性,不需要提前声明属性


// ✅ JavaScript - 不需要声明

class MyComponent extends React.Component {

// 不需要声明 props

render() {

// 直接使用,父组件传什么就有什么

const { name, age, city } = this.props;

}

}


// ❌ Objective-C - 必须先声明

@interface MyView : UIView

  

@property (nonatomic, copy) NSString *name; // 必须声明

@property (nonatomic, assign) NSInteger age; // 必须声明

  

@end

Props 类型检查(可选)

虽然 JavaScript 不强制类型,但可以使用 PropTypes 进行运行时检查:


import PropTypes from 'prop-types';

  

class UserProfile extends React.Component {

static propTypes = {

userId: PropTypes.string.isRequired, // 必需,字符串

userName: PropTypes.string.isRequired, // 必需,字符串

age: PropTypes.number, // 可选,数字

onUpdate: PropTypes.func // 可选,函数

};

  

render() {

const { userId, userName, age, onUpdate } = this.props;

// ...

}

}

  

// 如果传错类型,开发环境会警告

<UserProfile userId={123} userName="张三" />

// ⚠️ 警告: userId 应该是 string,不是 number


二、State:组件内部状态

什么是 State?

State 是组件自己管理的内部数据,类似于 iOS 中的实例变量。

iOS vs React 对比

iOS (Objective-C):


@interface MyViewController : UIViewController

  

@property (nonatomic, assign) NSInteger count;

@property (nonatomic, assign) BOOL isLoading;

  

@end

  

@implementation MyViewController

  

- (void)viewDidLoad {

[super viewDidLoad];

self.count = 0;

self.isLoading = NO;

}

  

- (void)increaseCount {

self.count++; // 直接修改

[self updateUI]; // 手动更新 UI

}

  

- (void)updateUI {

self.label.text = [NSString stringWithFormat:@"%ld", (long)self.count];

}

  

@end

React:


class MyComponent extends React.Component {

// 定义 state

state = {

count: 0,

isLoading: false

};

  

increaseCount = () => {

// 必须使用 setState 修改

this.setState({ count: this.state.count + 1 });

// React 会自动重新渲染组件

};

  

render() {

const { count, isLoading } = this.state;

  

return (

<div>

<p>计数: {count}</p>

<button onClick={this.increaseCount}>增加</button>

</div>

);

}

}

State 的定义方式

方式 1:类字段语法(推荐)


class MyComponent extends React.Component {

state = {

count: 0,

name: '张三'

};

}

方式 2:Constructor 中定义


class MyComponent extends React.Component {

constructor(props) {

super(props);

this.state = {

count: 0,

name: '张三'

};

}

}

State 的修改规则

❌ 错误方式:直接修改


// 直接修改不会触发重新渲染

this.state.count = 10; // ❌ 错误

this.state.items.push('新项'); // ❌ 错误

✅ 正确方式:使用 setState


// 修改单个属性

this.setState({ count: 10 });

  

// 修改多个属性

this.setState({

count: 10,

name: '李四'

});

  

// 基于当前 state 计算新值

this.setState(prevState => ({

count: prevState.count + 1

}));

  

// 修改数组(创建新数组)

this.setState({

items: [...this.state.items, '新项']

});

State 解构赋值


render() {

// 从 state 解构

const { count, isLoading, items } = this.state;

  

return (

<div>

<p>计数: {count}</p>

{isLoading && <p>加载中...</p>}

<ul>

{items.map(item => <li key={item.id}>{item.name}</li>)}

</ul>

</div>

);

}


三、Props vs State 对比

核心区别

特性PropsState
数据来源父组件传入组件内部定义
能否修改只读可修改(用 setState)
定义位置父组件 JSX组件内 state = {}
用途接收外部配置管理组件状态
iOS 类比init 参数/依赖注入实例变量

完整示例


class ImagePreview extends React.Component {

// ========== STATE (内部状态) ==========

state = {

scale: 1.0, // 组件自己管理

isZooming: false, // 组件自己管理

startDistance: null // 组件自己管理

};

  

render() {

// ========== PROPS (外部传入) ==========

const {

visible, // 父组件控制是否显示

image, // 父组件传入图片 URL

onClose // 父组件传入关闭回调

} = this.props;

  

// ========== STATE (内部状态) ==========

const {

scale, // 组件内部的缩放比例

isZooming // 组件内部的缩放状态

} = this.state;

  

if (!visible) return null;

  

return (

<div className="preview">

<button onClick={onClose}>关闭</button>

<img

src={image}

style={{ transform: `scale(${scale})` }}

/>

</div>

);

}

  

// 修改 state

handleZoom = () => {

this.setState({ scale: 2.0 }); // ✅ 可以修改 state

};

  

// 不能修改 props

handleError = () => {

this.props.visible = false; // ❌ 错误!props 是只读的

this.props.onClose(); // ✅ 正确!调用父组件的方法

};

}

  

// 使用组件

class App extends React.Component {

state = {

showPreview: false,

currentImage: ''

};

  

openPreview = (url) => {

this.setState({

showPreview: true,

currentImage: url

});

};

  

closePreview = () => {

this.setState({ showPreview: false });

};

  

render() {

return (

<div>

<button onClick={() => this.openPreview('photo.jpg')}>

查看图片

</button>

  

<ImagePreview

visible={this.state.showPreview}

image={this.state.currentImage}

onClose={this.closePreview}

/>

</div>

);

}

}


四、解构赋值:批量提取属性

什么是解构赋值?

解构赋值是 ES6 的语法特性,用于从对象或数组中批量提取值。

基础语法

对象解构


// 传统方式

const visible = this.props.visible;

const image = this.props.image;

const onClose = this.props.onClose;

  

// 解构赋值(推荐)

const { visible, image, onClose } = this.props;

iOS 对比:


// Objective-C 没有解构语法,需要逐个赋值

@interface User : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

@property (nonatomic, copy) NSString *city;

@end

  

User *user = [[User alloc] init];

user.name = @"张三";

user.age = 25;

user.city = @"北京";

  

// 需要逐个取值

NSString *name = user.name;

NSInteger age = user.age;

NSString *city = user.city;

数组解构


const arr = [1, 2, 3];

  

// 传统方式

const first = arr[0];

const second = arr[1];

  

// 解构赋值

const [first, second, third] = arr;

console.log(first); // 1

console.log(second); // 2

console.log(third); // 3

解构赋值高级用法

1. 默认值


const { serviceUcId = '', shareItems = [] } = this.state;

  

// 如果 this.state 中没有这些属性

this.state = { APP_SOURCE: 'mobile' };

  

// 解构后

// serviceUcId = '' // 使用默认值

// shareItems = [] // 使用默认值

// APP_SOURCE = 'mobile' // 从 state 中取到值

2. 重命名


const props = { visible: true };

  

// 提取并重命名

const { visible: isShow } = props;

  

console.log(isShow); // true

console.log(visible); // undefined (变量不存在)

3. 嵌套解构


const user = {

name: '张三',

address: {

city: '北京',

street: '朝阳区'

}

};

  

// 嵌套解构

const { name, address: { city } } = user;

console.log(name); // '张三'

console.log(city); // '北京'

4. 剩余参数


const { name, age, ...rest } = {

name: '张三',

age: 25,

city: '北京',

job: '工程师'

};

  

console.log(name); // '张三'

console.log(age); // 25

console.log(rest); // { city: '北京', job: '工程师' }

React 中的常见用法


class MyComponent extends React.Component {

render() {

// 同时解构 props 和 state

const { visible, image, onClose } = this.props;

const { scale, isZooming } = this.state;

  

// 函数参数解构

const user = { name: '张三', age: 25 };

this.renderUser(user);

}

  

renderUser = ({ name, age }) => { // 参数直接解构

return (

<div>

<p>姓名: {name}</p>

<p>年龄: {age}</p>

</div>

);

};

}

解构 vs 不解构对比


// ❌ 不解构(繁琐)

render() {

return (

<div>

{this.props.visible && <img src={this.props.image} />}

<button onClick={this.props.onClose}>关闭</button>

<p>缩放: {this.state.scale}</p>

</div>

);

}

  

// ✅ 解构(简洁)

render() {

const { visible, image, onClose } = this.props;

const { scale } = this.state;

  

return (

<div>

{visible && <img src={image} />}

<button onClick={onClose}>关闭</button>

<p>缩放: {scale}</p>

</div>

);

}


五、组件方法与事件处理

箭头函数 vs 普通函数

为什么用箭头函数?

问题: 普通函数中的 this 容易丢失


class MyComponent extends React.Component {

// 普通函数

handleClick() {

console.log(this.props); // undefined ❌

}

  

render() {

return <button onClick={this.handleClick}>点击</button>;

}

}

解决方案 1: 在 constructor 中绑定


class MyComponent extends React.Component {

constructor(props) {

super(props);

this.handleClick = this.handleClick.bind(this); // 绑定 this

}

  

handleClick() {

console.log(this.props); // ✅ 正常

}

}

解决方案 2: 使用箭头函数(推荐)


class MyComponent extends React.Component {

// 箭头函数自动绑定 this

handleClick = () => {

console.log(this.props); // ✅ 正常

};

  

render() {

return <button onClick={this.handleClick}>点击</button>;

}

}

iOS 对比:


@interface MyViewController : UIViewController

@end

  

@implementation MyViewController

  

- (void)viewDidLoad {

[super viewDidLoad];

  

UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];

[button addTarget:self

action:@selector(handleClick:)

forControlEvents:UIControlEventTouchUpInside];

}

  

- (void)handleClick:(UIButton *)sender {

// self 总是指向 MyViewController 实例

NSLog(@"%@", self.view);

}

  

@end

事件处理方法

无参数事件


handleClick = () => {

console.log('按钮被点击');

this.setState({ count: this.state.count + 1 });

};

  

render() {

return <button onClick={this.handleClick}>点击</button>;

}

传递参数


handleItemClick = (item) => {

console.log('点击了:', item);

this.setState({ selectedItem: item });

};

  

render() {

return (

<div>

{this.state.items.map(item => (

<div

key={item.id}

onClick={() => this.handleItemClick(item)}

>

{item.name}

</div>

))}

</div>

);

}

事件对象


handleInputChange = (event) => {

const value = event.target.value;

this.setState({ inputValue: value });

};

  

render() {

return (

<input

value={this.state.inputValue}

onChange={this.handleInputChange}

/>

);

}

阻止事件冒泡


handleShareClick = (e, item) => {

e.stopPropagation(); // 阻止冒泡到父元素

this.shareItem(item);

};

  

render() {

return (

<div onClick={this.handleCardClick}>

<button onClick={(e) => this.handleShareClick(e, item)}>

分享

</button>

</div>

);

}


六、SSR 数据获取:getInitialProps

什么是 getInitialProps?

在 SSR(Server-Side Rendering)项目中,getInitialProps 是一个特殊的静态方法,用于在服务端渲染前获取数据

完整数据流


// ========== 1. 服务端 (Node.js) ==========

// src/actions/worker/pages/standard-share-library.js

export default {

async handler(ctx) {

// 1.1 从接口获取数据

const res = await ProxyApis.constructionShareApi.getConfigItem(ctx, {});

const shareItems = res.data;

  

// 1.2 传给 KSSR 渲染

const renderContent = await kssr.render('worker/standard-share-library/index', {

shareItems,

projectOrderId: ctx.query.id,

APP_SOURCE: getAppSource(ctx)

});

  

ctx.body = renderContent.body;

}

};

  

// ========== 2. React 组件 ==========

class StandardShareLibrary extends React.Component {

// 2.1 接收服务端数据

static async getInitialProps({ shareItems, projectOrderId, APP_SOURCE }) {

// 可以在这里做数据处理

return {

shareItems, // → 变成 this.props.shareItems

projectOrderId, // → 变成 this.props.projectOrderId

APP_SOURCE // → 变成 this.props.APP_SOURCE

};

}

  

// 2.2 组件使用数据

render() {

const { shareItems, APP_SOURCE } = this.props;

  

return (

<div>

{shareItems.map(item => (

<div key={item.id}>{item.title}</div>

))}

</div>

);

}

}

getInitialProps 的用途

1. 原样返回(最常见)


static async getInitialProps({ shareItems, projectOrderId, APP_SOURCE }) {

// 服务端传什么,就返回什么

return { shareItems, projectOrderId, APP_SOURCE };

}

  

// 等价简写

static async getInitialProps(props) {

return props;

}

2. 数据转换


static async getInitialProps({ shareItems, projectOrderId, APP_SOURCE }) {

return {

// 过滤掉无效的分享项

shareItems: shareItems.filter(item => item.enabled),

projectOrderId,

APP_SOURCE

};

}

3. 额外数据获取


static async getInitialProps({ shareItems, projectOrderId, APP_SOURCE }) {

// 在服务端额外获取数据

const projectDetail = await fetch(`/api/project/${projectOrderId}`);

  

return {

shareItems,

projectOrderId,

projectDetail, // 新增的数据

APP_SOURCE

};

}

4. 数据验证


static async getInitialProps({ shareItems, projectOrderId, APP_SOURCE }) {

if (!projectOrderId) {

throw new Error('缺少项目 ID');

}

  

return {

shareItems: shareItems || [], // 提供默认值

projectOrderId,

APP_SOURCE

};

}


七、组件渲染方法

render 方法解析


render() {

// 1. 获取数据

const { visible, image, onClose } = this.props;

const { scale } = this.state;

  

// 2. 条件判断

if (!visible) {

return null; // 不渲染任何内容

}

  

// 3. 计算动态样式

const wrapperStyle = {

width: `${scale * 100}%`

};

  

// 4. 返回 JSX

return (

<div className="preview">

<button onClick={onClose}>关闭</button>

<div style={wrapperStyle}>

<img src={image} alt="预览图" />

</div>

</div>

);

}

条件渲染

1. if-return


render() {

if (!this.props.visible) {

return null;

}

  

return <div>内容</div>;

}

2. 三元表达式


render() {

return (

<div>

{this.props.visible ? <Content /> : <Empty />}

</div>

);

}

3. 逻辑与 &&


render() {

return (

<div>

{this.props.visible && <Content />}

{this.props.error && <ErrorMessage />}

</div>

);

}

列表渲染


render() {

const { items } = this.props;

  

return (

<div>

{items.map(item => (

<div key={item.id} className="item">

<h3>{item.title}</h3>

<p>{item.description}</p>

</div>

))}

</div>

);

}

自定义渲染方法


class MyComponent extends React.Component {

// 渲染单个卡片

renderCard = (item) => {

return (

<div key={item.id} className="card">

<img src={item.image} />

<h3>{item.title}</h3>

<button onClick={() => this.handleClick(item)}>

查看

</button>

</div>

);

};

  

// 主渲染方法

render() {

const { items } = this.props;

  

return (

<div className="card-list">

{items.map(item => this.renderCard(item))}

</div>

);

}

}


总结

核心概念速查表

概念说明类比 iOS (OC)
Props父组件传入的数据,只读init 参数 / property 注入
State组件内部状态,可修改实例变量
解构赋值批量提取对象属性-
箭头函数自动绑定 this-
render渲染组件 UIviewDidLoad + 更新UI
getInitialPropsSSR 数据获取-

最佳实践

  1. Props 用于接收外部数据,State 用于管理内部状态

  2. 优先使用解构赋值,让代码更简洁

  3. 组件方法使用箭头函数,避免 this 绑定问题

  4. State 修改必须用 setState,不能直接赋值

  5. Props 和 State 的命名要清晰,见名知意