创建项目
npx create-react-app <name of project>
使用TS
npx create-react-app my-app --template typescript
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
使用pnpm, vite创建项目:
pnpm create vite my-react-app --template react
1.class 换成 className,(现在可以不用这样,直接class,但不推荐)
<header className="App-header">
2.backgound-color 换成 backgoundColor
3.属性的值用字符串表示,这里用单引号'',社区规定:html中有的属性值用"",没有的用''
3.多个属性用 , 来连接
<button style={{ backgoundColor:'blue',color:'white'}}>
4.for 改成 htmlFor
由于 for 在 JavaScript 中是保留字,所以 React 元素中使用了 htmlFor 来代替。
<label htmlFor="name"></label>
创建节点
index.js
import { createRoot } from 'react-dom/client';
const App = () => {
return <div>hi there</div>
}
// ReactDOM.render(<App/>,document.querySelector('#root'))
const container = document.getElementById('app');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<App tab="home" />);
状态初始化
简写:
class App extends React.Component {
state = { lat: null, errorMessage: '' }
}
默认状态
const Spinner = props => {
return (
<div className="ui active dimmer">
<div className="ui big text loader">{props.message}</div>
</div>
)
}
Spniner.defaultProps = {
message: 'Loading...'
}
export default Spinner
生命周期
主要就三个,分别代表 DOM 挂载前, 更新时, 卸载时
componentDidMount()
componentDidUpdate()
componentWillUnmount()
事件监听
class SearchBar extend React.Component {
handleInputChange(event){
console.log(event.target.value)
}
render(){
return (
<div className="ui segment">
<form className="ui form">
<div className="field">
<label>Image Search</label>
<input type="text" onChange={this.onInputChange} />}
</div>
</form>
</div>
)
}
}
export default SearchBar
发送事件
import React from "react";
class SearchBar extends React.Component {
state = { term: ''}
onFormSubmit = (event) => {
event.preventDefault()
this.props.onSubmit(this.state.term)
}
render(){
return (
<div className="ui segment">
<form className="ui form" onSubmit={(e)=>{this.onFormSubmit(e)}}>
<div className="field">
<label>Image Search</label>
<input
type="text"
value={this.state.term}
onChange={e=>this.setState({ term: e.target.value})}
/>
</div>
</form>
</div>
)
}
}
export default SearchBar
事件冒泡
原生的优先级最高
react事件次之
hooks
useState
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
通过声明依赖跳过 Effect, 进行性能优化
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
useReducer
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
一个常见的用例便是命令式地访问子组件:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useCallback 与 useMemo
记忆钩子, 与useCallback 不同的是, useCallback 不会执行第一个参数,二是将它返回给你,而useMemo 会执行第一个函数并且将函数执行结果返回给你
import React, { FC, useCallback, useMemo, useState } from 'react';
const Index: FC = (props) => {
const [count, setCount] = useState(0);
const isEvenNumber = useMemo(() => {
return count % 2 === 0;
}, [count]);
const onClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<div>{count} is {isEvenNumber ? 'even':'odd'} number</div>
<button onClick={onClick}></button>
</div>
);
};
如果不使用 hook,每次组件 re-render 的时候,都需要重新计算 isEvenNumber 的值,以及 new 一个 onClick 函数,即使每次计算结果没有改变,也要重复这个浪费内存的操作,hook 可以缓存相关结果,避免重复渲染时的无效计算。
useCallback 和 useMemo 的参数都是一个函数加一个依赖数组,依赖没有改变时直接返回内存中缓存的结果,无需重复计算。简单理解就是 useCallback 缓存事件处理函数,useMemo 缓存二次计算的结果,如上面的点击事件,以及通过 count 值判断奇数偶数的二次计算结果。
自定义 Hook
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
useContext
useContext 让你不使用组件嵌套就可以订阅 React 的 Context。
创建
function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
// ...
}
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。
Provider 注入
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
Consumer 接收
theme-context.js
// 确保传递给 createContext 的默认值数据结构是调用的组件(consumers)所能匹配的!
export const ThemeContext = React.createContext({
theme: themes.dark, toggleTheme: () => {},});
theme-toggler-button.js
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
// Theme Toggler 按钮不仅仅只获取 theme 值, // 它也从 context 中获取到一个 toggleTheme 函数 return (
<ThemeContext.Consumer>
{({theme, toggleTheme}) => ( <button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;
多个 Context
为了确保 context 快速进行重渲染,React 需要使每一个 consumers 组件的 context 在组件树中成为一个单独的节点。
// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');
// 用户登录 context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// 提供初始 context 值的 App 组件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 一个组件可能会消费多个 context
function Content() {
return (
<ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer> );
}
注意事项
因为 context 会根据引用标识来决定何时进行渲染(本质上是 value 属性值的浅比较),所以这里可能存在一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每一次 Provider 重渲染时,由于 value 属性总是被赋值为新的对象,以下的代码会重新渲染下面所有的 consumers 组件:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}> <Toolbar />
</MyContext.Provider>
);
}
}
为了防止这种情况,将 value 状态提升到父节点的 state 里:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'}, };
}
render() {
return (
<MyContext.Provider value={this.state.value}> <Toolbar />
</MyContext.Provider>
);
}
}
路由
嵌套路由
//import ReactDOM from "react-dom/client";
import {
BrowserRouter,
Routes,
Route,
} from "react-router-dom";
import App from "./App";
import Expenses from "./routes/expenses";
import Invoices from "./routes/invoices";
const root = ReactDOM.createRoot(
document.getElementById("root")
);
root.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route path="expenses" element={<Expenses />} />
<Route path="invoices" element={<Invoices />} />
</Route>
</Routes>
</BrowserRouter>
);
路由传值
使用两个hook来做这件事,分别是 useLocation, useSearchParams
useReducer
初始化数据容器
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
传组件
有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。
我们建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
这使得别的组件可以通过 JSX 嵌套,将任意组件作为子组件传递给它们。
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
异常捕获边界(Error boundaries)
如果模块加载失败(如网络问题),它会触发一个错误。你可以通过异常捕获边界(Error boundaries)技术来处理这些情况,以显示良好的用户体验并管理恢复事宜。
import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
捕获组件错误
如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) { // 更新 state 使下一次渲染能够显示降级后的 UI return { hasError: true }; }
componentDidCatch(error, errorInfo) { // 你同样可以将错误日志上报给服务器 logErrorToMyService(error, errorInfo); }
render() {
if (this.state.hasError) { // 你可以自定义降级后的 UI 并渲染 return <h1>Something went wrong.</h1>; }
return this.props.children;
}
}
然后你可以将它作为一个常规组件去使用:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
错误边界的工作方式类似于 JavaScript 的 catch {},不同的地方在于错误边界只针对 React 组件。只有 class 组件才可以成为错误边界组件。大多数情况下, 你只需要声明一次错误边界组件, 并在整个应用中使用它。
注意 错误边界仅可以捕获其子组件的错误 ,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScript 中 catch {} 的工作机制。
Fragments
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
短语法
你可以使用一种新的,且更简短的语法来声明 Fragments。它看起来像空标签:
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}
支持的事件
React 通过将事件 normalize 以让他们在不同浏览器中拥有一致的属性。
以下的事件处理函数在冒泡阶段被触发。如需注册捕获阶段的事件处理函数,则应为事件名添加 Capture。例如,处理捕获阶段的点击事件请使用 onClickCapture,而不是 onClick。
- Clipboard Events
- Composition Events
- Keyboard Events
- Focus Events
- Form Events
- Generic Events
- Mouse Events
- Pointer Events
- Selection Events
- Touch Events
- UI Events
- Wheel Events
- Media Events
- Image Events
- Animation Events
- Transition Events
- Other Events
泛型组件
interface Props<T> {
list: T[],
renderItem: (item: T, i: number) => any
}
function MapList<T>({ list = [], renderItem }: Props<T>) {
return (
<div>
{list.map(renderItem)}
</div>
);
}
复制代码
使用子组件,
<MapList<ListVO>
list={list}
renderItem={(item) => (...)}
/>
复制代码
当然,因为ts的泛型可以根据自己推导类型,我们可以不用手动传递类型,
<MapList
list={list}
renderItem={(item) => (...)}
/>
异步
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}