学习如何轻松构建可伸缩的 React 应用程序:编写组件
# 命名规范
编程中常见的命名方式有:
驼峰式命名法(Camel Case),也叫小驼峰式命名法(Lower Camel Case)
代码语言:javascript复制const camelCase = "camelCase";
帕斯卡命名法(Pascal Case),也叫大驼峰式命名法(Upper Camel Case)
代码语言:javascript复制const PascalCase = "PascalCase";
在 React 中,组件的命名方式是大驼峰式命名法,即组件的名称必须以大写字母开头。
代码语言:javascript复制import React from "react";
const MyComponent = () => {
return <div>My Component</div>;
};
export default MyComponent;
# 函数式组件
函数组件是普通的 JavaScript 函数,它接收 props
作为输入并返回一个 React 组件。
可以是一个箭头函数:
代码语言:javascript复制import React from "react";
const MyComponent = (props) => {
return <div>My Component</div>;
};
export default MyComponent;
或普通函数:
代码语言:javascript复制import React from "react";
function MyComponent(props) {
return <div>My Component</div>;
}
export default MyComponent;
函数组件具有一些常见的 hook 。React hooks 使得大多数开发人员能够构建可伸缩的 React 应用程序。
# useState
useState
是 React 中最常用的 hook 之一,它用于在函数式组件中存储状态值(对象、字符串、布尔值等),这些值在组件的生命周期中进行变更。useState
接受一个初始值,如果是字符串则可以为空字符串,这个值可以在组件的生命周期中进行更新。
import React, { useState } from "react";
const MyComponent = () => {
const [name, setName] = useState("");
return (
<div>
<h1>My Component</h1>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
};
export default MyComponent;
# useRef
useRef
方法也是大多数函数组件中常用的 React hooks 之一。useRef
方法常用于指向 DOM 中的一个元素,可用于创建不受控制的元素。
import React, { useRef } from "react";
const MyComponent = () => {
const inputRef = useRef(null);
const browseFile = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
return (
<div>
<input type="file" ref={inputRef} />
<button onClick={browseFile}>Browse File</button>
</div>
);
};
export default MyComponent;
# useEffect
useEffect
方法也是大多数功能组件中常用的 React hook 。useEffect
方法是一种异步钩子,让我们可以在组件上执行异步任务,这些异步任务包括调用 API 并通过 useState
保存数据。
useEffect
接受两个参数,分别是:
- 带有可选的返回语句的函数
- 可选的返回语句是一个函数,它在组件卸载时执行,用于进行清理工作,如定时器、事件监听器等
- 可选的依赖项数组
- 当不传入依赖项数组时,
useEffect
会在每次渲染时执行 - 当传入依赖项数组时
- 如果数组为空,则
useEffect
只会在组件挂载时执行 - 如果数组不为空,则
useEffect
会在组件挂载时执行,以及当数组中的任何值发生变化时执行
- 如果数组为空,则
- 当不传入依赖项数组时,
import React, { useState, useEffect } from "react";
const MyComponent = () => {
const [name, setName] = useState("");
const [count, setCount] = useState(0);
useEffect(() => {
console.log("render component");
});
useEffect(() => {
console.log("componentDidMount");
return () => {
console.log("componentWillUnmount");
};
}, []);
useEffect(() => {
console.log("componentDidUpdate");
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<h1>My Component</h1>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={() => setCount(count 1)}>Click Me</button>
</div>
);
};
export default MyComponent;
# 类组件
类组件是继承自 React.component
的子类组件,这个类组件接受 props
并渲染它们,它以一个 constructor
开始,这个 constructor
会被超类调用。
import React from "react";
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>My Component</div>;
}
}
export default MyComponent;
类组件有一些常见的生命周期方法:
componentDidMount
componentDidUpdate
# componentDidMount
该生命周期方法在 React 组件生命周期的挂载阶段被调用,这个方法可以帮助我们在向用户展示数据之前修改 React 组件的内容。
代码语言:javascript复制import React from "react";
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
};
}
componentDidMount() {
this.setState({ name: "Cell" });
}
render() {
return <div>My name is {this.state.name}</div>;
}
}
export default MyComponent;
# componentDidUpdate
该生命周期方法在组件更新后被调用:
代码语言:javascript复制import React from "react";
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
};
}
componentDidMount() {
this.setState({ name: "Cell" });
}
componentDidUpdate() {
console.log("componentDidUpdate");
}
render() {
return <div>My name is {this.state.name}</div>;
}
}
export default MyComponent;
# 组件结构最佳实践
React 组件是构建小型到强大应用程序的方式。这些 React 组件需要以良好的方式进行结构化,以便于进行测试、扩展和易于发现错误。
以下是保持良好的 React 组件结构的最佳方法:
- 避免使用大型组件
- 大型组件通常很难阅读、理解和调试
- 即使应用程序正常运行,当出现问题时,如何调试也将是个问题
- 应该将大型组件分解为较小的组件,以便于阅读、测试和轻松识别错误
- 给组件和变量合适的命名
- 编写合理的变量名、方法名或组件名非常重要
- 避免使用模糊不清的命名
- 保持文件夹结构精确和易于理解
- 文件和文件夹结构在实现良好的组件结构方面也非常重要
- 为项目提供文件夹结构,以便于理解应该将哪些文件放入特定文件夹中
- 将可重用的逻辑移至单独的类或函数中
- 通常在编程中,始终记住 DRY 原则
- 无论您觉得应用程序或组件将使用哪些可重用的逻辑,都将其移至函数或方法中,并在应用程序中调用
- 尝试编写测试
- 测试可以确保您的组件按预期工作,并在编写得当时减少应用程序中的错误数量
# 组件数据共享
在 React 中,一定会在在组件之间共享数据,具体实现方式取决于状态变化的复杂程度和应用程序的大小。
以下是一些实现方式:
Props
Context
APIRedux
useReducer
# Props
Props
是在 React 中从一个组件传递数据到另一个组件的一种方式,props
是从父组件传递到子组件的对象。Props
是 properties
(属性)的缩写。
import React from "react";
const MyComponent = (props) => {
const { name, age } = props;
return (
<div>
<p>My name is {name}</p>
<p>My age is {age}</p>
</div>
);
};
const App = () => {
return <MyComponent name="Cell" age={18} />;
};
export default App;
# Context API
Context API
也是一种从一个组件传递数据到另一个组件的方式。与 Props
的主要区别在于,Context API
不会在每个组件上从父组件传递到子组件。
Context API
有两个主要方法:
Provider
Provider
接受一个要传递给子组件的值
Consumer
Consumer
允许调用组件订阅context
更新
import React from "react";
const MyContext = React.createContext();
const MyComponent = () => {
return (
<MyContext.Consumer>
{(data) => (
<div>
<p>My name is {data.name}</p>
<p>My age is {data.age}</p>
</div>
)}
</MyContext.Consumer>
);
};
const MyComponent2 = () => {
const data = React.useContext(MyContext);
return (
<div>
<p>My name is {data.name}</p>
<p>My age is {data.age}</p>
</div>
);
};
const App = () => {
return (
<MyContext.Provider value={{ name: "Cell", age: 18 }}>
<MyComponent />
<MyComponent2 />
</MyContext.Provider>
);
};
export default App;
# Redux
Redux 是一个开源的 JavaScript 库,它保持全局状态以使应用程序具有一致的行为。
Redux 库包括以下三个部分:
- Store
- 用于存储全局状态
- 这一部分是不可变的,即它无法改变
- Reducer
- Reducer 是一个纯函数,它接受两个参数(初始状态和操作),并返回一个新的状态
- Actions
- Action 是一个 JavaScript 对象,告诉 Reducer 用户希望在 Store 中执行什么操作
- Action 是用户的指令,用于在 Store 中要么更改状态,要么创建状态的副本
import { createStore } from "redux";
const initialState = {
name: "",
age: 0,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "SET_NAME":
return {
...state,
name: action.payload,
};
case "SET_AGE":
return {
...state,
age: action.payload,
};
default:
return state;
}
};
const store = createStore(reducer);
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch({ type: "SET_NAME", payload: "Cell" });
// { name: "Cell", age: 0 }
store.dispatch({ type: "SET_AGE", payload: 18 });
// { name: "Cell", age: 18 }
# useReducer
useReducer
方法也是在组件之间共享数据的一种方式。当您需要进行复杂状态更改时,可以使用 useReducer
方法。
useReducer
方法接受参数为初始状态和操作,返回当前状态和 dispatch
方法。
import { useReducer } from "react";
const initialState = {
name: "",
age: 0,
};
const reducer = (state, action) => {
switch (action.type) {
case "SET_NAME":
return {
...state,
name: action.payload,
};
case "SET_AGE":
return {
...state,
age: action.payload,
};
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>My name is {state.name}</p>
<p>My age is {state.age}</p>
<button onClick={() => dispatch({ type: "SET_NAME", payload: "Cell" })}>Set Name</button>
<button onClick={() => dispatch({ type: "SET_AGE", payload: 18 })}>Set Age</button>
</div>
);
};
export default App;
# 受控组件 vs 非受控组件
受控组件数据是由 React 组件管理的,而非受控组件数据是由 浏览器或 DOM 处理。受控组件通常由用户输入或事件处理。
代码语言:javascript复制import React, { useState, useRef } from "react";
const controlledComponent = () => {
const [name, setName] = useState("");
const [age, setAge] = useState(0);
return (
<div>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
<input type="number" value={age} onChange={(e) => setAge(e.target.value)} />
</div>
);
};
const uncontrolledComponent = () => {
const nameRef = useRef();
const ageRef = useRef();
const handleSubmit = () => {
console.log(nameRef.current.value);
console.log(ageRef.current.value);
};
return (
<div>
<input type="text" ref={nameRef} />
<input type="number" ref={ageRef} />
<button onClick={handleSubmit}>Submit</button>
</div>
);
};
const App = () => {
return (
<div>
<controlledComponent />
<uncontrolledComponent />
</div>
);
};
export default App;