React 是一个用于构建用户界面的 JavaScript 库
import {createRoot} from 'react-dom/client'
import App from './App'
const elm = document.getElementById('app')
const root = createRoot(elm);
root.render(<App />);
npx create-react-app my-app
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
export class Hello extends Component {
...
}
export default function World() {
/* ... */
}
使用 export
导出 Hello
,export default
导出 World
组件
import World, { Hello } from './hello.js';
使用 import
导入 Hello
组件,在示例中使用。
import React from "react";
import "./Student.css";
export const Student = (
<div className="Student"></div>
);
注意:类属性 className
const divStyle = {
backgroundImage: 'url(' + imgUrl + ')',
};
export const Student = (
<div style={divStyle}></div>
);
<Student name="Julie" age={23}
pro={true} />
函数组件 Student
中访问属性
function Student(props) {
return <h1>Hello, {props.name}</h1>;
}
Class 组件 Student
中访问属性
class Student extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
class
组件使用 this.props
访问传递给组件的属性。
function Example() {
return (
<AlertBox>
<h1>您有待处理的通知</h1>
</AlertBox>
)
}
函数 AlertBox
组件
function AlertBox(props) {
return (
<div className="alert-box">
{props.children}
</div>
);
}
{props.children}
Class AlertBox
组件,与函数组件 AlertBox
组件相同
class AlertBox extends React.Component {
render () {
return (
<div className="alert-box">
{this.props.children}
</div>
);
}
}
{this.props.children}
children
作为子组件的的属性传递。
函数中的 State,Hook 是 React 16.8 的新增特性
import { useState } from 'react';
function Student() {
const [count, setCount] = useState(0);
const click = () => setCount(count + 1);
return (
<div>
<p>您点击了 {count} 次</p>
<button onClick={click}>
点击我
</button>
</div>
);
}
使用 setState
更新状态,下面是函数组件读取状态
<p>您点击了 {count} 次</p>
import React from 'react';
class Student extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
// 确保函数可以访问组件属性(ES2015)
this.click = this.click.bind(this);
}
click() {
const count = this.state.count;
this.setState({ count: count + 1})
}
render() {
return (
<div>
<button onClick={this.click}>
点击我
</button>
<p>您点击了{this.state.count}次</p>
</div>
);
}
}
使用 setState
更新状态,class
组件中不能使用 hooksclass
组件读取状态
<p>您点击了{this.state.count}次</p>
const elm = ['one', 'two', 'three'];
function Student() {
return (
<ul>
{elm.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
);
}
key
值在兄弟节点之间必须唯一
export default function Hello() {
function handleClick(event) {
event.preventDefault();
alert("Hello World");
}
return (
<a href="/" onClick={handleClick}>
Say Hi
</a>
);
}
function addNumbers(x1, x2) {
return x1 + x2;
}
const element = (
<div>
{addNumbers(2, 5)}
</div>
);
import { useState } from 'react'
import Avatar from './Avatar';
import Profile from './Profile';
function Student() {
const [count, setCount] = useState(0);
return (
<div>
<Avatar src={count} />
<Profile username={count} />
</div>
);
}
React 并_没有_创建一个新的 div
。它只是把子元素渲染到 domNode
中。domNode
是一个可以在任何位置的有效 DOM 节点。
render() {
return ReactDOM.createPortal(
this.props.children,
domNode
);
}
提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案
import { Fragment } from 'react'
import Avatar from './Avatar';
import Profile from './Profile';
const Student = () => (
<Fragment>
<Avatar src="./demo.jpg" />
<Profile username="name" />
</Fragment>
);
从 v16.2.0
开始 Fragment
可用于返回多个子节点,而无需向 DOM
添加额外的包装节点。或者使用 <></>
效果是一样的。
const Student = () => (
<>
<Avatar src="./demo.jpg" />
<Profile username="name" />
</>
);
render() {
return 'Look ma, no spans!';
}
您可以只返回一个字符串。查看: Fragments & strings
const Student = () => [
<li key="A">First item</li>,
<li key="B">Second item</li>
];
不要忘记 key
!查看:
Fragments & strings
const FancyButton = React.forwardRef(
(props, ref) => (
<button ref={ref} className="btn">
{props.children}
</button>
)
);
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>
点击我
</FancyButton>;
import {Component,createRef} from 'react'
class MyComponent extends Component {
constructor(props) {
super(props);
this.myRef = createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
提示:Refs 适用于类组件,但不适用于函数组件(除非您使用 useRef hook,请参阅hooks)
function CustomTextInput(props) {
// 这里必须声明 $input,这样 ref 才可以引用它
const $input = useRef(null);
function handleClick() {
$input.current.focus();
}
return (
<div>
<input type="text" ref={$input} />
<input
type="button" value="聚焦文本输入"
onClick={handleClick}
/>
</div>
);
}
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
突出显示应用程序中潜在问题的工具。请参阅:严格模式
测量一个 React 应用多久渲染一次以及渲染一次的 代价
<Profiler id="Navigation" onRender={callback}>
<Navigation {...props} />
</Profiler>
为了分析 Navigation
组件和它的子代。应该在需要时才去使用它。
:- | :- |
---|---|
id(string) |
发生提交的 Profiler 树的 id |
onRender(function) |
组件树任何组件 “提交” 一个更新的时候调用这个函数 |
:- | :- |
---|---|
phase: "mount" | "update" |
判断是由 props /state /hooks 改变 或 “第一次装载”
引起的重渲染
|
actualDuration: number |
本次更新在渲染 Profiler 和它的子代上花费的时间 |
baseDuration: number |
在 Profiler 树中最近一次每一个组件 render 的持续时间 |
startTime: number |
本次更新中 React 开始渲染的时间戳 |
commitTime: number |
本次更新中 React commit 阶段结束的时间戳 |
interactions: Set |
当更新被制定时,“interactions” 的集合会被追踪 |
class CustomButton extends React.Component {
// ...
}
CustomButton.defaultProps = {
color: 'blue'
};
<CustomButton /> ;
不传值 props.color
将自动设置为 blue
class Hello extends Component {
constructor (props) {
super(props)
this.state = { visible: true }
}
}
在构造 constructor()
中设置默认状态。
class Hello extends Component {
state = { visible: true }
}
function CustomButton(props) {
const { color = 'blue' } = props;
return <div>{color}</div>
}
function CustomButton() {
const [color, setColor]=useState('blue')
return <div>{color}</div>
}
JSX
仅仅只是
React.createElement(component, props, ...children)
函数的语法糖
<MyButton color="blue" shadowSize={2}>
点击我
</MyButton>
会编译为
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'点击我'
);
没有子节点
<div className="sidebar" />
会编译为
React.createElement(
'div',
{className: 'sidebar'}
)
const Menu = ({ children }) => (
<div className="menu">{children}<div>
);
Menu.Item = ({ children }) => (
<div>{children}<div>
);
<Menu>
<Menu.Item>菜单一</Menu.Item>
<Menu.Item>菜单二</Menu.Item>
<Menu>
let element = <h1>Hello, world!</h1>;
let emptyHeading = <h1 />;
const root = ReactDOM.createRoot(
document.getElementById('root')
);
const element = <h1>Hello, world</h1>;
root.render(element);
参考:渲染元素
const avatarUrl = "img/picture.jpg"
const element = <img src={avatarUrl} />;
const element = (
<button className="btn">
点击我
</button>
);
注意:类属性 className
let name = '张三';
let element = <h1>Hello, {name}</h1>;
function fullName(firstName, lastName) {
return firstName + ' ' + lastName;
}
let element = (
<h1>
Hello, {fullName('三', '张')}
</h1>
);
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function MyComponent() {
return <div style={divStyle}>组件</div>;
}
const markup = {__html: '我 · 你' };
const MyComponent = () => (
<div dangerouslySetInnerHTML={markup} />
);
dangerouslySetInnerHTML
是 React 为浏览器 DOM 提供 innerHTML
的替换方案。
const MyComponent = () => (
<div>
<input type="radio" id="ab" name="v">
<label for="ab">HTML</label>
</div>
);
for
在 JS
中是保留字,JSX 元素使用了 htmlFor
代替
非受控组件的属性,设置组件第一次挂载时的 value
<textarea defaultValue="Hello" />
<input>
、<select>
和 <textarea>
支持 value
属性
非受控组件的属性,设置组件是否被选中
<input type="radio" defaultChecked />
类型为 checkbox
或 radio
时,组件支持 checked 属性
import React from "react";
function formatName(user) {
return user.firstName
+ ' '
+ user.lastName;
}
export function Greeting(user) {
if (user) {
return (
<h1>你好, {formatName(user)}!</h1>
);
}
return (
<h1>你好, 先生。</h1>
);
}
注意:组件必须总是返回一些东西。
<Greeting firstName="三" lastName="张" />
export default function Weather(props) {
const isLoggedIn = props.isLoggedIn;
return (
<div>
<b>{isLoggedIn ? '已' : '未'}</b>登录。
</div>
);
}
{isShow && <div>内容</div>}
<Dropdown>
下拉列表
<Menu>
<Menu.Item>菜单一</Menu.Item>
<Menu.Item>菜单二</Menu.Item>
<Menu.Item>菜单三</Menu.Item>
</Menu>
</Dropdown>
组件名称以大驼峰式命名。
function Greeting(props) {
let button;
if (props.isLoggedIn) {
button = <UserGreeting />;
} else {
button = <GuestGreeting />;
}
return <div>{button}</div>;
}
function Student() {
const [count, setCount] = useState(0);
return (
<Fragment>
{/* 这里写注释 */}
</Fragment>
);
}
import React from 'react';
const UserName = () => <h1>Kenny</h1>;
export default function UserProfile() {
return (
<div className="UserProfile">
<div>Hello</div>
<UserName />
</div>
);
}
注意:每个组件都需要一个根元素,更多说明。
class Welcome extends React.Component {
render() {
return <h1>{this.props.name}</h1>;
}
}
:- | - |
---|---|
this.forceUpdate() |
强制重新渲染 |
this.setState({ ... }) |
更新状态 |
this.setState(state =>{ ... }) |
更新状态 |
:- | - |
---|---|
defaultProps |
默认 props |
displayName |
显示组件名称(用于调试) |
:- | - |
---|---|
this.props |
组件接受参数 |
this.state |
组件内状态 |
import React, {PureComponent} from 'react'
class MessageBox extends PureComponent {
···
}
import React, { Component } from 'react';
// 高阶组件 with
const with = data => WrappedComponent => {
return class extends Component {
constructor(props) {
super(props);
}
render() {
return (
<WrappedComponent data={data} />
)
}
}
}
使用高阶组件
const LowComponent = (props) => (
<div>{props.data}</div>
);
const MyComp = with('Hello')(LowComponent)
function FancyBorder(props) {
return (
<div className={'Fancy'+props.color}>
{props.children}
</div>
);
}
组件可以通过 JSX 嵌套
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="title">欢迎</h1>
<p className="message">
感谢您访问我们的宇宙飞船
</p>
</FancyBorder>
);
}
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="left">
{props.left}
</div>
<div className="right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={<Contacts />}
right={<Chat />}
/>
);
}
给组件 SplitPane
传递 left
和 right
两个组件参数
import React from 'react';
import UserAvatar from "./UserAvatar";
export default function UserProfile() {
return (
<div className="UserProfile">
<UserAvatar />
<UserAvatar />
</div>
);
}
注意:假设 UserAvatar
在 UserAvatar.js
中声明
const Menu = ({ children }) => (
<div className="menu">{children}<div>
);
Menu.Item = ({ children }) => (
<div>{children}<div>
);
<Menu>
<Menu.Item>菜单一</Menu.Item>
<Menu.Item>菜单二</Menu.Item>
<Menu>
方法 | 描述 |
---|---|
useState |
返回一个 state ,更新 state 的函数
#
|
useEffect |
可能有副作用代码的函数 # |
useContext |
接收并返回该 context 的当前值
#
|
方法 | 描述 |
---|---|
useReducer |
useState 的替代方案
#
|
useCallback |
返回一个回调函数 # |
useMemo |
返回一个 memoized 值# |
useRef |
返回一个可变的 ref 对象
#
|
useImperativeHandle |
暴露给父组件的实例值 # |
useLayoutEffect |
DOM 变更后同步调用函数 # |
useDebugValue |
开发者工具中显示标签 # |
useDeferredValue |
接受并返回该值的新副本 # |
useTransition |
过渡任务的等待状态 # |
useId |
用于生成唯一 ID # |
方法 | 描述 |
---|---|
useSyncExternalStore |
读取和订阅外部数据源 # |
useInsertionEffect |
DOM 突变之前 同步触发 # |
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
function TextInputWithFocusButton() {
const $input = useRef(null);
const onButtonClick = () => {
$input.current.focus();
};
return (
<>
<input ref={$input} type="text" />
<button onClick={onButtonClick}>
聚焦输入
</button>
</>
);
}
current
指向已挂载到 DOM 上的文本输入元素
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
父组件使用
<FancyInput ref={inputRef} />
inputRef.current.focus()
useEffect(() => {
const subs = props.source.subscribe();
return () => {
subs.unsubscribe();
};
}, [props.source]);
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
const memoizedValue = useMemo(
() => {
return computeExpensiveValue(a, b)
},
[a, b]
);
function Checkbox() {
const id = useId();
return (
<>
<label htmlFor={id}>
你喜欢React吗?
</label>
<input id={id} type="checkbox" />
</>
);
};
用于生成跨服务端和客户端稳定的唯一 ID
的同时避免 hydration
不匹配
function useFriendStatus(friendID) {
const [
isOnline, setIsOnline
] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(
isOnline ? 'Online' : 'Offline'
);
return isOnline;
}
不推荐你向每个自定义 Hook
添加 debug
值
useEffect(
() => {
// componentDidMount
// 组件挂载时,可以在这里完成你的任务
return () => {
// componentWillUnmount
// 卸载时执行,清除 effect
};
},
[ ]
);
这是一个类似 class
组件中 componentDidMount
&
componentWillUnmount
两个生命周期函数的写法。
方法 | 描述 |
---|---|
constructor (props) |
渲染前 # |
static getDerivedStateFromProps() |
调用 render 方法之前调用
#
|
render() |
class 组件中唯一必须实现的方法
#
|
componentDidMount() |
在组件挂载后(插入 DOM 树中)立即调用 # |
UNSAFE_componentWillMount() |
在挂载之前被调用,建议使用 constructor()
#
|
在 constructor()
上设置初始状态。在 componentDidMount()
上添加 DOM
事件处理程序、计时器(等),然后在 componentWillUnmount()
上删除它们。
方法 | 描述 |
---|---|
componentWillUnmount() |
在组件卸载及销毁之前直接调用 # |
方法 | 描述 |
---|---|
static getDerivedStateFromProps(props, state) |
调用 render 之前调用,在初始挂载及后续更新时都会被调用
#
|
shouldComponentUpdate(nextProps, nextState) |
如果返回 false ,则跳过 render()
#
|
render() |
在不修改组件 state 的情况下,每次调用时都返回相同的结果
#
|
getSnapshotBeforeUpdate() |
在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置) # |
componentDidUpdate() |
这里使用 setState() ,但记得比较 props 。首次渲染不会执行此方法
#
|
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以显降级 UI
return { hasError: true };
}
render() {
if (this.state.hasError) {
// 你可以渲染任何自定义的降级 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.uid !== prevProps.uid) {
this.fetchData(this.props.uid);
}
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我们是否在 list 中添加新的 items ?
// 捕获滚动位置以便我们稍后调整滚动位置。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
import PropTypes from 'prop-types'
:- | - |
---|---|
any |
任意类型 |
(props, propName, 组件名称)=>{} |
自定义验证器 |
:- | - |
---|---|
string |
字符串 |
number |
数组 |
func |
函数 |
bool |
布尔值 |
symbol |
- |
:- | - |
---|---|
oneOf(any) |
枚举类型 |
oneOfType([type]) |
几种类型中的任意一个类型 |
:- | - |
---|---|
array |
数组 |
arrayOf |
数组由某一类型的元素组成 |
:- | - |
---|---|
object |
对象 |
objectOf |
对象由某一类型的值组成 |
instanceOf(...) |
类的实例 |
shape |
对象由特定的类型值组成 |
exact |
有额外属性警告 |
:- | - |
---|---|
element |
React 元素 |
elementType |
React 元素类型(即 MyComponent ) |
node |
DOM 节点 |
:- | - |
---|---|
(···).isRequired |
必需的 |
MyComponent.propTypes = {
email: PropTypes.string,
seats: PropTypes.number,
callback: PropTypes.func,
isClosed: PropTypes.bool,
any: PropTypes.any
symbol: PropTypes.symbol,
}
你可以将属性声明为 JS 原生类型,默认都是可选的。
MyComponent.propTypes = {
// 确保这个 prop 没有被提供时,会打印警告信息
requiredFunc: PropTypes.func.isRequired,
// 任意类型的必需数据
requiredAny: PropTypes.any.isRequired,
}
你可以在任何 PropTypes
属性后面加上 isRequired
。
MyComponent.propTypes = {
// 只能是特定的值,枚举类型。
optionalEnum: PropTypes.oneOf([
'News', 'Photos'
]),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
}
MyComponent.propTypes = {
// 任何可被渲染的元素
// (包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
node: PropTypes.node,
// 一个 React 元素。
element: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)
elementType: PropTypes.elementType,
}
MyComponent.propTypes = {
// 可以指定一个对象由某一类型的值组成
objectOf: PropTypes.objectOf(
PropTypes.number
),
// 可以指定一个对象由特定的类型值组成
objectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// 带有额外属性警告的对象
objectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
}
MyComponent.propTypes = {
custom: (props, propName, compName) => {
if (!/matchm/.test(props[propName])) {
// 它在验证失败时应返回一个 Error 对象
return new Error(
'无效的prop `'
` \`${propName}\` 提供给` +
` \`${compName}\`。验证失败。`
);
}
},
}
请不要使用 console.warn
或抛出异常,因为这在 oneOfType
中不会起作用。
arrayOf
或 objectOf
验证器
MyComponent.propTypes = {
arrayProp: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => {
if (!/matchme/.test(propValue[key])) {
// 它应该在验证失败时返回一个 Error 对象。
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
}
propValue
是数组或对象本身,key
是他们当前的键。
MyComponent.propTypes = {
arr: PropTypes.arrayOf(PropTypes.number),
};
可以指定一个数组由某一类型的元素组成
MyComponent.propTypes = {
message: PropTypes.instanceOf(Message),
};
声明 message
为类的实例