跳到主要内容

React

项目结构

|- node_modules
|- src: 存放代码
|- index.js
|- public:存放一些不需要编译打包的静态文件
|- asset: 存放需要参与编译打包的静态文件
|- index.html
|- package.json

tsx 语法入门

什么是 tsx ?

jsx(javascript and xml) 是 js 的语法扩展,允许在 js 中写 html 代码。

tsx 是在 jsx 的基础上增加了类型。

JSX 语法

HTML-like 语法

const ele = <h1>Hello JSX!</h1>;

嵌入 js 表达式

const name = 'wangzhy';
const ele = <h1>hello, {name}</h1>;

属性命名规范

  • 使用 cameCase 语法代替 HTML 属性命
  • 动态属性通过 {} 传递
const divEle = <div className="container" tabIndex="1"></div>;

<img src={user.avatarUrl}/>

自闭合标签

必须严格闭合标签,例如 <img/> 不能写成 <img>

只能有一个根元素

const ele = (
<div>
<h1>title</h1>
<p>content</p>
</div>
);

jsx 常见用法

条件渲染

{
isLoggedIn && <UserPanel/>
}
{
score > 60 ? <PassIcon/> : <FailIcon/>
}

列表渲染

li 标签的 key 是必须要的,不然会影响性能。

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

事件绑定

<button onClick={handleClick}>Click</button>

样式处理

  1. 内联样式:<div style={{ color: 'red', fontSize: 16 }}></div>
  2. CSS 类:<div className="my-class"></div>

与组建结合

  1. 自定义组件:组件名必须已大写字母开头,以便与 HTML 原生标签区分
  2. 传递 props:<Button color="blue" text="Click me" />
  3. 子元素:使用 props.children 获取嵌套内容

JSX 底层转换

jsx 会被 babel 或 typescript 转译为 React.createElement() 调用。

<div className="box">Hello</div>

转换为:

React.createElement("div", { className: "box" }, "Hello");

每一个 JSX 元素对应一个 虚拟 DOM 对象

虚拟 DOM 对象,是由框架内部构建的一套对象体系,基于这些对象,框架可以构建出真实 DOM 对象

只有第一次渲染页面时是直接从 虚拟 DOM 渲染为 真实 DOM ,后续 View 更新时,需要经过 DOM-DIFF 计算出两次视图差异的地方, 渲染差异DOM

函数组件

组件命名一般采用 PascalCase 大驼峰。

定义一个函数组件

一个函数返回的是 jsx 元素,这就是一个函数组件。

/*函数式组件*/
const DemoOne = function () {
/*返回一个 jsx 元素*/
return <div className="demo-box">demo one </div>
};

export default DemoOne;

Props 传参

props 传递过来的参数是 只读 的, 不允许修改, props.xxx='abc'; 是会报错的。

原因:props 是被冻结的。 Object.isFrozen(props) == true

传递动态值与表达式

父组件调用子组件的时候,可以基于属性,把不同的信息传递给子组件,子组件接收相应的属性值,呈现出不同的效果,让组件的复用性更强。

<Child
score={95}
isStudent={true}
hobbies={['music', 'reading']}
info={{id: 1, role: 'admin'}}
onClick={() => console.log('Click...')}
/>

基础传参

// 父组件
const Parent = () => {
return <Child name="Alice" age={25}/>;
};

// 子组件
const Child = (props) => {
return <div>{props.name} is {props.age} years old.</div>;
};

解构 props

const Child = ({name, age}) => {
return <div>{name} is {age} years old.</div>;
};

默认参数值

通过 js 的对象解构语法 {} 来实现组件参数的默认值设置。

// 方式一:函数参数默认值(推荐)
const Child = ({name = "Guest", age = 18}) => {
// ...
};

通过把函数当做对象,设置静态的私有属性方法,来给其设置属性的默认值。

// 方式二:defaultProps
const DemoFour = function (props) {
let {title, x} = props;
console.log(title, x);
return <div className="demo">
<h2 className="title">{title}</h2>
</div>
}

DemoFour.defaultProps = {
x: 0
};

export default DemoFour;

Children 属性

通过 props.children 获取组件嵌套的内容(文本、元素或其他组件):

// 父组件
<Child>
<span>This is children content.</span>
</Child>

// 子组件
const Child = ({children}) => {
return <div>{children}</div>;
};

PropTypes 类型校验

import PropTypes from 'prop-types';

const Child = ({name, age}) => {
// ...
};

Child.propTypes = {
name: PropTypes.string.isRequired, // 必须为字符串且必传
age: PropTypes.number,
onLogin: PropTypes.func,
info: PropTypes.shape({
id: PropTypes.number,
role: PropTypes.oneOf(['user', 'admin'])
})
};

扩展运算符传递 Props

const user = {name: 'Bob', age: 30};

// 父组件
<Child {...user} />

// 等同于:<Child name="Bob" age={30} />

进阶传参方式

Context API 跨层级传参

通过 createContext 和 useContext 解决深层组件 prop drilling 问题:

// 创建 Context
const UserContext = React.createContext();

// 父组件提供数据
const App = () => {
return (
<UserContext.Provider value={{name: 'Charlie'}}>
<Child/>
</UserContext.Provider>
);
};

// 深层子组件消费数据
const Child = () => {
const user = React.useContext(UserContext);
return <div>{user.name}</div>;
};

使用 Hook 管理状态与传参

借助 useState、useReducer 等 Hook 动态传递状态:

const Parent = () => {
const [count, setCount] = React.useState(0);
return <Child count={count} onIncrement={() => setCount(c => c + 1)}/>;
};

const Child = ({count, onIncrement}) => {
return <button onClick={onIncrement}>Count: {count}</button>;
};

高阶组件(HOC)或自定义 Hook 通过 HOC 或自定义 Hook 抽离逻辑,复用参数处理逻辑:

// 自定义 Hook:处理用户数据
const useUser = () => {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetchUser().then(setUser);
}, []);
return {user};
};

// 子组件使用 Hook 获取数据
const Child = () => {
const {user} = useUser();
return <div>{user?.name}</div>;
};

对比类组件的传参差异

  • 接收方式:类组件通过 this.props 访问,函数式组件直接通过参数获取。
  • 性能优化:函数式组件可通过 React.memo() 缓存,避免不必要的渲染:
const MemoizedChild = React.memo(Child);

总结

React 函数式组件的传参方式灵活且直观,结合现代 Hook API,能够高效处理静态数据、动态状态及跨层级通信。其优势在于:

  • 代码简洁性:解构、默认值等特性减少冗余代码。
  • 强类型支持:通过 PropTypes 或 TypeScript 增强代码可靠性。
  • 逻辑复用:自定义 Hook 或 Context 实现复杂数据流管理。
  • 性能优化:借助 React.memo 和 Hook 依赖项控制渲染。

troubleshooting

非空断言

为什么 main.tsx 中 document.getElementById('root')! 要加一个 ! ?

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

这里的 ! 表示非空断言。 即告诉编译器,这个表达式的结果一定不为空!

public 与 assets 目录的区别。

public 打包之后,会放到根目录, assets 会跟随项目一起被编译。

React 组件被渲染 2 次

在开发环境下,当使用 React 的 <StrictMode> 包裹组件时,React 会故意渲染 2 次组件。

  1. 强制让开发者提前发现渲染过程中的副作用(如不纯的渲染函数)。
  2. 通过对比两次渲染结果,确保组件的幂等性(多次渲染结果一致)。