JSX
JSX
是在JavaScript
语法上的拓展,允许 HTML
代码和 JS
一起写。
///单行代码
const heading = Mozilla Developer Network ;
///多行代码
const header = (
Mozilla Developer Network
);
///heading/header 常量称为 JSX 表达式
///React 可以使header在我们的应用程序中进行渲染
JSX
浏览器无法直接读取并解析,JSX
表达式,经过parcel
或babel
编译后:
const header = React.createElement("header", null,
React.createElement("h1", null, "Mozilla Developer Network")
);
实际开发中也可以跳过编译步骤,直接使用React.createElement()
构建UI
。
创建React应用
npx
包运行器;npm
包管理器
创建react
应用并启动
# 创建react应用模板
npx create-react-app react-demo
# 如果既有yarn又有npm,在创建时可以指定使用哪个创建
npx create-react-app react-demo --use-npm
#启动react应用
cd react-demo
#运行应用在http://localhost:3000
npm start
在React
中,组件是组成应用程序的可重复利用的模块。
组件 & Props
函数组件与class组件
// 函数组件
function TestComponent(props) {
return Hi , {props.value}
}
///ES6 class组件
class TestComponent extends React.Component {
render(){
return Hi , {this.props.value}
}
}
注意: 组件名称必须以大写字母开头。小写字母开头的组件视为原生 DOM
标签
Props 的只读性
///纯函数,a、b的值不会被修改
function sum(a, b) {
return a + b;
}
所有React 组件都必须像纯函数一样保护它们的 props 不被更改。
State&生命周期
///计时器组件
class Clock extends React.Component {
///构造函数
constructor(props) {
super(props)
///初始时间状态 & 计数器状态
this.state = {
date: new Date(),
counter: 1
}
}
///返回组件的JSX
render(){
return (
当前时间:{this.state.date.toLocaleTimeString()}
时间更新次数:{this.state.counter}
)
}
///生命周期函数
///组件已经被渲染到DOM中时调用
componentDidMount() {
const timerHandler = ()=>{
///错误示范
// this.setState({
// counter: this.state.counter + this.props.increment,
// });
//联合更新,使用箭头函数,方式如下:
this.setState((state,props)=>({
date: new Date(),
counter: state.counter += parseInt(props.increment)
}))
// 联合更新,使用普通函数,方式如下:
this.setState(function(state,props){
return {
date: new Date(),
counter: state.counter += parseInt(props.increment)
}
})
///简单更新
this.setState({
date: new Date()
})
}
///启动定时器
this.timerId = setInterval(timerHandler,2000)
console.log("componentDidMount")
}
///组件更新时调用
componentDidUpdate(){
console.log("componentDidUpdate")
}
///组件被销毁之前调用
componentWillUnmount(){
clearInterval(this.timerId)
console.log("componentWillUnmount")
}
}
State
的更新可能是异步的,因此不能使用在setState
中使用this.state
。 组件可以选择把它的 state
作为 props
向下传递到它的子组件中:
function FormattedDate(props) {
return It is {props.date.toLocaleTimeString()}. ;
}
事件处理
React 事件的命名采用小驼峰式(camelCase),而不是纯小写
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
<--- 传统html --->
Activate Lasers
<---React写法--->
Activate Lasers
事件添加
///普通函数
class LoggingButton extends React.Component {
constructor(){
super()
///传建一个和原函数体相同的函数,这个函数和this进行绑定
this.handleClick = this.handleClick.bind(this)
}
// 若不绑定严格模式下,未bind,则this指向undifined
handleClick(){
console.log('this is:', this);
}
render() {
return (
Click me
///或者直接绑定
Click me
);
}
}
///箭头函数
class LoggingButton extends React.Component {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
// 注意: 这是 *实验性* 语法。
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
Click me
);
}
}
向事件处理函数传参
///普通函数
handleClick(id){
console.log('this is:', this);
console.log(id)
}
Click me
///箭头函数
{this.handleClick(2)}}>
Click me
条件渲染
元素变量
//声明一个变量并使用 if 语句进行条件渲染
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = ;
} else {
button = ;
}
return (
{button}
);
}
&&
运算符
JavaScript
中,true && expression
总是会返回 expression
, 而 false && expression
总是会返回 false
。如果条件是 true
,&&
右侧的元素就会被渲染,如果是 false
,React
会忽略并跳过它。
render() {
const count = 0;
return (
{count &&
Messages: {count} }
);
}
三目运算符
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
{isLoggedIn
?
:
}
);
}
阻止组件渲染
在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render
方法直接返回 null
,而不进行任何渲染。
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
Warning!
);
}
///其他组件引用`WarningBanner`,当warn为空,该组件不被渲染
render() {
return (
{this.state.showWarning ? 'Hide' : 'Show'}
);
}
列表&Key
const root = ReactDOM.createRoot(document.getElementById('root'));
const numbers = [1, 2, 3, 4, 5];
root.render(
);
function List(param) {
///集合的`Map`转换
///key 帮助 React 识别哪些元素改变了
const listItem = param.values.map((num,index)=>{num} )
return
///或者
return {param.values.map((num,index)=>{num} )}
}
用Key
提取组件
元素的 key
只有放在就近的数组上下文中才有意义。 比方说,如果你提取出一个 ListItem
组件,你应该把 key 保留在数组中的这个
元素上,而不是放在 ListItem
组件中的
元素上。 错误示例:
function ListItem(props) {
const value = props.value;
return (
// 错误!你不需要在这里指定 key:
{value}
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 错误!元素的 key 应该在这里指定:
);
return (
);
}
正确示例:
///正确示例
function ListItem(props) {
// 正确!这里不需要指定 key:
return {props.value} ;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在数组的上下文中被指定
);
return (
);
}
组合&继承
组件使用一个特殊的 children
属性, 来将他们的子组件传递到组件中。
function FancyBorder(props) {
return (
{props.children}
);
}
function WelcomeDialog() {
return (
///children
Welcome
);
}
也可以自定义属性
function FancyBorder(props) {
return (
{props.content}
);
}
function WelcomeDialog() {
const content = Welcome
return (
);
}
代码分割
为了避免搞出大体积的代码包,需要进行代码分割。 代码分割是由诸如 Webpack,Rollup 和 Browserify(factor-bundle)这类打包器支持的一项技术,能够创建多个包并在运行时动态加载。
import()
///使用之前
import { add } from './math';
console.log(add(16, 26));
///使用之后
import("./math").then(math => {
console.log(math.add(16, 26));
});
React.lazy
React.lazy
函数能让我们像渲染常规组件一样处理动态引入的组件。
///before
import OtherComponent from './OtherComponent';
///after
const OtherComponent = React.lazy(() => import('./OtherComponent'));
此代码将会在组件首次渲染时,自动导入包含 OtherComponent
组件的包。React.lazy
接受一个函数,这个函数需要动态调用 import()
。它必须返回一个 Promise
,该 Promise
需要 resolve
一个 default export
的 React
组件。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
//fallback 属性接受任何在组件加载过程中你想展示的 React 元素
正在加载...
}>
);
}
Context
Context
提供了一个无需为每层组件手动添加 props
,就能在组件树间进行数据传递的方法。实现了一个组件树“全局”数据的共享。 通过挂载在class
上的 contextType
属性可以赋值为由 React.createContext()
创建的 Context
对象。此属性可以让你使用 this.context
来获取最近 Context
上的值。
///创建context并设置初始值
const ThemeContext = React.createContext('light')
export default class App extends React.Component {
render() {
return (
///ThemeContext.Provider 指定value修改初始值
)
}
}
// 中间的组件不必指明往下传递 props
export class TabBar extends React.Component {
render() {
return
}
}
/// 叶子节点直接获取
export class Navigation extends React.Component {
///必须以`contextType`进行指定,使得`this.context`能够获取
static contextType = ThemeContext;
render() {
console.log(Navigation.contextType)
return (
Naigation 的主题 : {this.context}
)
}
}
消耗多个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 (
);
}
}
function Layout() {
return (
);
}
// 一个组件可能会消费多个 context
function Content() {
return (
{theme => (
{user => (
)}
)}
);
}
错误边界
部分 UI
的 JavaScript
错误不应该导致整个应用崩溃,为了解决这个问题,React 16
引入了一个新的概念 —— 错误边界。
错误边界是一种 React
组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI ,而并不会渲染那些发生崩溃的子组件树。错误边界可以捕获发生在整个子组件树的渲染期间 、生命周期方法 以及构造函数 中的错误。
注意 :错误边界无法捕获以下场景中产生的错误:
事件处理
异步代码
服务端渲染
它自身抛出的错误
示例代码1:
//定义的错误边界组件
export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = {
errorInfo: null,
error: null
}
}
componentDidCatch(err, errInfo) {
// Catch errors in any components below and re-render with error message
this.setState({
error: err,
errorInfo: errInfo,
})
// You can also log error messages to an error reporting service here
}
render() {
if (this.state.errorInfo) {
return (
出错了
{this.state.error && this.state.error.toString()}
{this.state.errorInfo && this.state.errorInfo.toString()}
)
}
return this.props.children
}
}
///使用函数组件
function Counter(params) {
///"React.useState" cannot be called in a class component,
/// must be called in a React function component or a custom React Hook function
const [count, setCount] = React.useState(0)
function click1() {
let x = count + 1
setCount(x)
}
if (count === 2) {///模仿错误
throw new Error("Crashed")
}else{
return {count}
}
}
示例代码2
///定义边界组件
export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = {
hasError : false
}
}
static getDerivedStateFromError(error) {
return { hasError: true }
}
render() {
if (this.state.hasError) {
return 出错了
}
return this.props.children
}
}
///使用类组件
export default class Counter extends React.Component{
constructor(props) {
super(props);
this.state = { counter:0 }
}
handleClick = ()=>{
///***********注意括号******
this.setState(({counter}) => ({
counter: counter + 1
}));
}
render() {
return {this.state.counter}
}
}
最终使用
///代码分割,懒加载
const Error = React.lazy(() => import('./ErrorBoundary.jsx'))
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
text1
///与懒加载配合
加载中...
} >
text2
);
注意:自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。
Refs
Refs
提供了一种方式,允许我们访问DOM
节点或在 render
方法中创建的 React
元素。
Refs
是使用 React.createRef()
创建的,并通过 ref
属性附加到 React
元素。在构造组件时,通常将 Refs
分配给实例属性,以便可以在整个组件中引用它们。
///为DOM元素添加`Ref`
export default class RefComponent extends Component {
constructor(props) {
super(props);
///创建`Ref`
this.pRef = React.createRef()
}
onClick = ()=>{
const pNode = this.pRef.current ///获取pNode
pNode.textContent = '通过Refs操作添加的文本' ///操作元素
}
render() {
return (
)
}
}
React
会在组件挂载时给 current
属性传入 DOM
元素,并在组件卸载时传入 null
值。ref
会在 componentDidMount
或 componentDidUpdate
生命周期钩子触发前更新。
// 为`React`的class组件`RefComponent` 添加`ref`。
// 在组件加载后,通过该组件的`ref`,执行其内部的点击事件,触发组件内部的更新
export class AutoRefComponet extends Component {
constructor(props) {
super(props);
this.comRef = React.createRef()
}
componentDidMount(){
this.comRef.current.onClick()
}
render() {
return ;
}
}
AutoRefComponet
通过Ref
操作RefComponent
组件,实现其内部P
标签的更新,仅当RefComponent
组件为Class
组件时有效。
默认情况下,不能在函数组件上使用 ref
属性 ,但是可通过Refs
转发或将函数组件转换为Class
组件来使用Ref
,前提条件这个ref
只能指向一个DOM
元素或 class
组件。
///举例:函数组件内部使用`ref`,非属性
export default function RefComponent(){
///创建
const pRef = useRef(null)
function onClick(){
const pNode = pRef.current ///获取pNode
pNode.textContent = '通过Refs操作添加的文本' ///操作元素
}
return (
)
}
Refs回调
另一种设置ref
的方式,不同于传递 createRef()
创建的 ref
属性,需要传递一个函数。这个函数中接受 React
组件实例或 HTML DOM
元素作为参数,以使它们能在其他地方被存储和访问。
export default class RefComponent extends Component {
constructor(props) {
super(props);
///存储pnode
this.pNode = null
///创建ref 回调
this.pRef = (element) => {
///返回元素 dom or react 实例
this.pNode = element
}
}
onClick = ()=>{
this.pNode.textContent = '通过Refs操作添加的文本' ///操作元素
}
render() {
return (
)
}
}
React
将在组件挂载时,会调用 ref
回调函数并传入 DOM
元素,当卸载时调用它并传入 null
。
可以在组件间传递回调形式的 refs
,就像你可以传递通过 React.createRef()
创建的对象 refs
一样。
export default class RefComponent extends Component {
render() {
return (
)
}
}
// 为`React`的class组件,添加`ref`回调
export class AutoRefComponet extends Component {
componentDidMount(){
this.pNode.textContent = '通过Refs操作添加的文本'
}
render() {
/// 注意:不能是ref,换个名
return {
this.pNode = element
}}/>;
}
}
Refs转发
修改一个子组件,需要使用新的props
来重新渲染它,但是某些情况下需要强制修改,被修改的子组件可能是React
组件或DOM
元素。
比如上述的AutoRefComponet
组件通过ref
直接引用RefComponet
组件,从而操作子组件, 16.3
或更高版本的 React
,推荐使用Ref
转发。 建议尽量使用状态提升来处理。
Ref 转发是一个可选特性,其允许某些组件接收 ref
,并将其向下传递(换句话说,“转发”它)给子组件。
///普通的函数组件另一种定义形式
const RefComponent = (props) => (
)
///ref转发示例
///子组件
const RefComponent = React.forwardRef((props, ref) => (
)
)
// 父组件
export class AutoRefComponet extends Component {
constructor(props) {
super(props)
this.getPref = React.createRef()
}
componentDidMount() {
this.getPref.current.textContent = '通过Refs转发操作添加的文本'
}
render() {
return ;
}
}
Fragment
///横向列表显示 hello world
///上述方式比较复杂,React提供了简单的方式
class Columns extends React.Component {
render() {
return (
Hello
World
);
}
}
///简写
class Columns extends React.Component {
render() {
return (
<>
Hello
World
>
);
}
}
高阶组件
高阶组件(HOC
)是 Reac
t 中用于复用组件逻辑的一种高级技巧。HOC
自身不是React API
的一部分,它是一种基于 React
的组合特性而形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
详情
高阶组件
高阶组件(HOC
)是 Reac
t 中用于复用组件逻辑的一种高级技巧。HOC
自身不是React API
的一部分,它是一种基于 React
的组合特性而形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
详情
注意事项
不要在 render 方法中使用 HOC
如果从 render
返回的组件与前一个渲染中的组件相同(===
),则 React
通过将子树与新子树进行区分来递归更新子树。 如果它们不相等,则完全卸载前一个子树。
render() {
// 每次调用 render 函数都会创建一个新的 EnhancedComponent
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
return ;
}
不仅仅出现性能问题 ,重新挂载组件会导致该组件及其所有子组件的状态丢失。
务必复制静态方法
React
组件上定义静态方法很有用,但将 HOC
应用于组件时,原始组件将使用容器组件进行包装,新组件会没有原始组件的任何静态方法。
// 定义静态函数
WrappedComponent.staticMethod = function() {/*...*/}
// 现在使用 HOC
const EnhancedComponent = enhance(WrappedComponent);
// 增强组件没有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
解决办法
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
// 必须准确知道应该拷贝哪些方法 :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
render props
render prop 是一个用于告知组件需要渲染什么内容的函数 prop
///图片组件,跟着鼠标移动
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
);
}
}
///鼠标移动的组件
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
{/*
使用 `render`prop 动态决定要渲染的内容,
而不是给出一个 渲染结果的静态表示
*/}
{this.props.render(this.state)}
);
}
}
class MouseTracker extends React.Component {
render() {
return (
移动鼠标!
///告诉mouse组件需要渲染cat组件,也可以指定其他的组件
(
)}/>
);
}
}
重要的是要记住,render prop
是因为模式才被称为 render prop ,你不一定要用名为 render
的 prop
来使用这种模式。事实上任何被用于告知组件需要渲染什么内容的函数 prop
在技术上都可以被称为 render prop
类型检查
const root = ReactDOM.createRoot(document.getElementById('root'));
const tmp = [12,33,2]
root.render(
);
///定义
import PropTypes from 'prop-types'
export default class MyComponent extends Component {
///设置默认值
static defaultProps = {
name : "我的组件"
}
///类型校验
static propTypes = {
name : PropTypes.string
}
constructor(props) {
super(props);
}
render() {
return (
{this.props.name}
);
}
}
///函数组件使用
import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
return (
Hello, {name}
)
}
///类型校验
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
///设置默认值
HelloWorldComponent.defaultProps = {
name: "我的组件"
}
export default HelloWorldComponent
非受控组件
在 React 中,
始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。
///通过DOM节点,读取数据
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
HOOK
Hook
是React 16.8
的新增特性。它可以让你在不编写class
的情况下使用state
以及其他的 React
特性
即,可以让你在函数组件里“钩入” React state
及生命周期等特性的函数。
useState
关于函数组件
const Example = (props) => {
return
;
}
///or
function Example(props) {
return
;
}
函数组件中使用useState
会返回一对值:当前状态和更新它的函数,我们可以在事件处理函数中或其他一些地方调用这个函数,可重设状态。
///函数组件使用useState实现状态管理
///"React.useState" cannot be called in a class component,
/// must be called in a React function component or a custom React Hook function
function Counter(params) {
/// 声明一个叫 “count” 的 state 变量
const [count, setCount] = React.useState(0)
function click1() {
let x = count + 1
///状态更新
setCount(x)
}
///状态读取
return {count}
}
///等价的class组件
///使用类组件
export default class Counter extends React.Component{
constructor(props) {
super(props);
this.state = { counter:0 }
}
handleClick = ()=>{
///***********注意括号******
this.setState(({counter}) => ({
counter: counter + 1
}));
//***或者可以这样***
this.setState((state) => ({
counter: state.counter + 1
}));
}
render() {
return {this.state.counter}
}
}
惰性初始State
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
JavaScript解构赋值语法
详情
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}
///useState的语法 like this
function f() {
return [1, 2];
}
let a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2
useEffect
useEffect
可看做 componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。具体定义:
type EffectCallback = () => (void | Destructor);
/*
@param effect 接收一个函数,该函数返回值为void,或返回一个 cleanup 函数
@param deps 可选参数,若有,effect函数仅在该列表中的值发生变化时,才会被执行
*/
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
useEffect
在第一次渲染之后和每次更新之后都会执行。React
保证了每次运行effect
的同时,DOM
都已经更新完毕。
function Counter(props) {
const [count, setCount] = React.useState(0)
function handleClick() {
setCount(count + 1)
}
///每次渲染都会执行effect, 每次effect都会清除上次的effect
React.useEffect(()=>{
document.title = `组件挂载|更新后,计数为:${count}`
const cleanUp = ()=>{
console.log(`组件卸载,计数为:${count}`);
}
return cleanUp
},[count])
return {count}
}
///等价的class组件
export class Counter extends React.Component{
constructor(props) {
super(props);
this.state = { count:0 }
}
componentDidMount(){
document.title = `组件挂载后,计数为:${this.state.count}`
}
componentDidUpdate(prevProps,prevState) { ///点击触发
if (prevState.count !== this.state.count) {
document.title = `组件更新时,计数为${this.state.count}`
}
}
componentWillUnmount(){
document.title = `组件卸载时,计数为${this.state.count}`
}
handleClick = ()=>{
this.setState((state) => ({
count: state.count + 1
}));
}
render() {
return {this.state.count}
}
}
多Effect
多个 Effect 实现关注点分离
function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
}
自定义HOOK
通过自定义Hook
,可以将组件逻辑提取到可重用的函数中。
在 React
中有两种流行的方式来共享组件之间的状态逻辑: render props 和高阶组件
自定义 Hook 是一个函数,其名称以 “use
” 开头,函数内部可以调用其他的 Hook
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
//通过friendID,查询是否在线
});
return isOnline;
}
///使用自定义的HOOK
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
{props.friend.name}
);
}
更多HOOK API。
参考资料
https://react.html.cn/docs/getting-started.html