React-从0开始-20260408

4 阅读4分钟

React

一、JSX 语法

1. JSX 是什么?

问题:JSX 是什么?

答案核心回答:JSX 是 JavaScript 的语法扩展,允许在 JavaScript 中编写类似 HTML 的标记。

代码示例

// JSX 元素
const element = <h1 className="title">Hello, World!</h1>;

// 表达式嵌入
const name = '张三';
const element = <p>你好,{name}!</p>;

// 属性
const element = <img src={user.avatarUrl} alt={user.name} />;

// 条件渲染
const element = isLoggedIn ? <UserGreeting /> : <GuestGreeting />;

// 列表渲染
const list = users.map(user => (
    <li key={user.id}>{user.name}</li>
));

二、React Hooks

2. useState

问题:useState

答案核心回答:useState 是 React Hook,用于在函数组件中添加状态。

代码示例

import { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);
    
    return (
        <div>
            <p>计数: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                增加
            </button>
            <button onClick={() => setCount(prev => prev + 1)}>
                函数式更新
            </button>
        </div>
    );
}

// 多个状态
const [name, setName] = useState('');
const [age, setAge] = useState(0);

// 对象状态
const [form, setForm] = useState({ name: '', email: '' });
setForm(prev => ({ ...prev, name: 'new name' }));

3. useEffect

问题:useEffect

答案核心回答:useEffect 处理副作用,类似于 componentDidMount/componentDidUpdate/componentWillUnmount。

代码示例

import { useEffect } from 'react';

function UserProfile({ userId }) {
    const [user, setUser] = useState(null);
    
    useEffect(() => {
        // 组件挂载后执行
        fetchUser(userId).then(setUser);
        
        // 清理函数
        return () => {
            console.log('组件卸载或更新前清理');
        };
    }, [userId]); // 依赖数组
    
    // 空依赖 - 只执行一次
    useEffect(() => {
        document.title = '加载中...';
    }, []);
    
    // 无依赖 - 每次渲染都执行
    useEffect(() => {
        console.log('每次渲染都执行');
    });
}

4. useCallback 与 useMemo

问题:useCallback 与 useMemo 的区别

答案核心回答:useCallback 缓存函数,useMemo 缓存计算结果。

代码示例

import { useCallback, useMemo } from 'react';

// useCallback - 缓存函数
const handleClick = useCallback(() => {
    doSomething(a, b);
}, [a, b]);

// useMemo - 缓存计算结果
const expensiveValue = useMemo(() => {
    return computeExpensiveValue(a, b);
}, [a, b]);

// 避免不必要的重渲染
function Parent() {
    const [count, setCount] = useState(0);
    
    // 不使用 useCallback,每次渲染都会创建新函数
    const handleClick = () => console.log('click');
    
    // 使用 useCallback,只有 count 变化时才创建新函数
    const handleClick = useCallback(() => {
        console.log(count);
    }, [count]);
    
    return <Child onClick={handleClick} />;
}

三、React 原理

5. Virtual DOM

问题:Virtual DOM

答案核心回答:虚拟 DOM 是真实 DOM 的 JavaScript 对象表示,通过 diff 算法最小化更新。

代码示例

// 虚拟 DOM 节点
const vdom = {
    type: 'div',
    props: { className: 'container' },
    children: [
        {
            type: 'h1',
            props: {},
            children: 'Hello'
        }
    ]
};

// React.createElement
const element = React.createElement(
    'div',
    { className: 'container' },
    React.createElement('h1', null, 'Hello')
);

// JSX 转换
// <div className="container"><h1>Hello</h1></div>
// 会被编译为 createElement 调用

6. React Diff 算法

问题:React Diff 算法

答案核心回答:React 通过 tree diff、component diff、element diff 三层比较实现高效更新。

代码示例

// tree diff - 同层比较
// <ul>
//   <li>A</li>
//   <li>B</li>
// </ul>
// 变为
// <ul>
//   <li>C</li>
//   <li>B</li>
// </ul>
// 只会比较 li,而不是整个 ul

// component diff - 类型比较
// 同类型组件继续比较 props
// 不同类型直接替换

// element diff - key 的作用
// 无 key:A-B-C -> A-B-C(每个都更新)
// 有 key:A-B-C -> C-A-B(只移动 C)
// <li key="1">A</li>
// <li key="2">B</li>
// <li key="3">C</li>

7. Fiber 架构

问题:Fiber 架构

答案核心回答:Fiber 将渲染工作拆分成小单元,实现可中断、可恢复的渲染。

代码示例

// Fiber 节点结构
const fiber = {
    type: 'div',
    key: null,
    stateNode: /* DOM 节点 */,
    child: /* 子 fiber */,
    sibling: /* 兄弟 fiber */,
    return: /* 父 fiber */,
    alternate: /* workInProgress */,
    effectTag: 'UPDATE', // PLACEMENT, DELETION, UPDATE
};

四、State 与生命周期

8. State 的使用

问题:State 的使用

答案核心回答:state 是组件私有数据,修改必须通过 setState。

代码示例

class Counter extends React.Component {
    state = {
        count: 0,
        message: 'Hello'
    };
    
    handleClick = () => {
        // 异步更新,可能被合并
        this.setState({ count: this.state.count + 1 });
        
        // 函数式更新,避免依赖当前 state
        this.setState(prevState => ({
            count: prevState.count + 1
        }));
        
        // setState 回调
        this.setState({ count: 1 }, () => {
            console.log('状态已更新');
        });
    };
}

五、组件与 Props

9. Props

问题:Props 的使用

答案核心回答:Props 是父组件传递给子组件的数据,只读。

代码示例

// Props 传递
<Welcome name="张三" age={25} />

// Props 验证
import PropTypes from 'prop-types';

Welcome.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    onClick: PropTypes.func,
    style: PropTypes.object,
    children: PropTypes.node
};

// 默认 Props
Welcome.defaultProps = {
    age: 18
};

10. 函数组件与类组件

问题:函数组件与类组件的区别

答案核心回答:函数组件是无状态组件,类组件是有状态组件(现在推荐使用 Hooks)。

代码示例

// 函数组件
function Welcome({ name }) {
    return <h1>Hello, {name}</h1>;
}

// 类组件
class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>;
    }
}

// Hooks 替代类组件状态
function Counter() {
    const [count, setCount] = useState(0);
    return <div>Count: {count}</div>;
}

六、Redux 状态管理

11. Redux 核心概念

问题:Redux 的核心概念

答案核心回答:Store、Action、Reducer 是 Redux 核心。

代码示例

// Action - 描述发生了什么
const ADD_TODO = 'ADD_TODO';
const action = {
    type: ADD_TODO,
    payload: { text: '学习 Redux' }
};

// Reducer - 根据 action 更新 state
function todos(state = [], action) {
    switch (action.type) {
        case ADD_TODO:
            return [...state, action.payload];
        default:
            return state;
    }
}

// Store - 存储唯一状态树
import { createStore } from 'redux';
const store = createStore(todos);

// 获取状态
store.getState();

// 派发 action
store.dispatch(action);

// 订阅变化
store.subscribe(() => console.log(store.getState()));

12. Redux 中间件

问题:Redux 中间件

答案核心回答:中间件扩展 dispatch 功能,处理异步 action。

代码示例

// redux-thunk
const thunkMiddleware = store => next => action => {
    if (typeof action === 'function') {
        return action(store.dispatch, store.getState);
    }
    return next(action);
};

// redux-saga
import { call, put, takeEvery } from 'redux-saga/effects';

function* fetchUserSaga(action) {
    try {
        const user = yield call(Api.fetchUser, action.payload);
        yield put({ type: 'FETCH_SUCCESS', payload: user });
    } catch (e) {
        yield put({ type: 'FETCH_ERROR', error: e.message });
    }
}

function* rootSaga() {
    yield takeEvery('FETCH_USER', fetchUserSaga);
}