一个只会
vue
的前端菜鸡学react
# react18 挂载
import { createRoot } from "react-dom/client";
const reactDom = createRoot(document.getElementById("root"));
reactDom.render(<div>HelloWorld</div>);
# JSX
# JSX 防止注入攻击
你可以安全地在 JSX 当中插入用户输入内容:
const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;
React DOM 在渲染所有输入内容之前,默认会进行转义 (opens new window)。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本) (opens new window)攻击。
特殊字符进行转义,以防止 xss 攻击
& becomes &
< becomes <
> becomes >
" becomes "
' becomes '
# 表达式
在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式 (opens new window)。例如,2 + 2
,user.firstName
或 formatName(user)
都是有效的 JavaScript 表达式。
比如在大括号中使用js
变量
const name = "Gauhar Chan";
const element = <h1>Hello, {name}</h1>;
jsx 也是表达式,Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用。上面的element
大概会进行这样的转换
const name = "Gauhar Chan";
const element = React.createElement("h1", {}, "Hello, " + name);
而element
这个React 元素
大概长这样
// 注意:这是简化过的结构
const element = {
type: "h1",
props: {
children: "Hello, Gauhar Chan",
},
};
# 组件 & props
# class 组件
jsx
中使用this.props
访问组件的props
import React from "react";
export default class HelloWorld extends React.Component {
render() {
return <div>HelloWorld {this.props.name}</div>;
}
}
# function 组件
该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。
export function HelloFuntion(props) {
return <div>HelloFuntion, {props.name}</div>;
}
# props
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
警告 ⚠️
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
即子组件不能直接修改父组件的props,单向数据流
# 修改props的值
上面有说到不能直接改,那就换个思路,交给父组件自己更新(因为state
是私有的,并且完全受控于当前组件。)。通过函数传参的方式实现。
子组件
import React from "react";
export default class HelloWorld extends React.Component {
handle () {
this.props.onUserChange({
name: 'gauhar chan'
})
}
render() {
return (<div onClick={this.handle.bind(this)}>HelloWorld { this.props.user.name }</div>)
}
}
父组件
handleChangeUser(user) {
// 修改值
this.setState({
user
})
}
<HelloWorld user={ this.state.user } onUserChange={ (user) => this.handleChangeUser(user)}/>
- 在父组件中定义了一个修改state的函数,并通过
props
传递给子组件 - 子组件监听了点击事件,最终通过传入参数并调用父组件的函数实现修改值。
提示
绑定事件的时候要注意,因为是react模板解析机制是上下文会是立即执行内容,所以要加上handle.bind(this)来指定上下文。
# 使用组件
上面的两个组件放在了
./components/helloWorld/index.jsx
文件中
import HelloWorld, { HelloFuntion } from "./components/helloWorld";
class Game extends React.Component {
render() {
return (
<div>
<HelloWorld name="chan" />
<HelloFuntion name="gauhar" />
</div>
);
}
}
const reactDom = createRoot(document.getElementById("root"));
reactDom.render(<Game />);
警告 ⚠️
注意: 组件名称必须以大写字母开头。
React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div />
代表 HTML 的 div 标签,而 <Welcome />
则代表一个组件,并且需在作用域内使用 Welcome
。
# State
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
class组件
使用state
,必须创建构造函数并通过super
将props
传递到父类的构造函数中
然后在该函数中为 this.state
赋初值
constructor(props) {
super(props);
this.state = {
name: 'gauhar chan'
}
}
# setState
通过this.setState
可以修改state
中的值。
得益于 setState()
的调用,React 能够知道 state 已经改变了,然后会重新调用 render()
方法来确定页面上该显示什么。
WARNING
构造函数是唯一可以给 this.state
赋值的地方。除此之外不要直接修改State。
# State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState()
调用合并成一个调用。
因为 this.props
和 this.state
可能会异步更新,所以不要依赖他们的值来更新下一个状态。
如需基于之前的 state 来设置当前的 state,可以让 setState()
接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
另外如果需要state修改值之后,执行某些东西,可以使用setState的第二个参数callback回调函数
this.setState({
name: 'gauhar'
}, () => {
// state.name更新后
})
TIP
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流 (opens new window),而不是尝试在不同组件间同步 state。
# 生命周期
# 事件处理
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
- 事件对象
event
是一个合成事件。React 根据 W3C 规范 (opens new window)来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。React 事件与原生事件不完全相同。如果想了解更多,请查看SyntheticEvent
(opens new window) 参考指南。
# 在class组件中要注意上下文
使用bind绑定this,默认
event
是最后一个参数隐式传递<div onClick={this.handle.bind(this, '参数')}>HelloWorld { this.props.user.name }</div>
使用一个函数返回,通常使用箭头函数简写,event需要手动显示的传递
<div onClick={(event) => this.handle('参数', event)}>HelloWorld { this.props.user.name }</div>
使用箭头函数有个注意的点,当这种用法被用于props传递给子组件,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。
class fields
目前还是实验性的语法,不过
Create React App
默认启用此语法。export default class HelloWorld extends React.Component { handle = () => { this.props.onUserChange({ name: 'gauhar chan' }) } render() { return (<div onClick={this.handle}>HelloWorld { this.props.user.name }</div>) } }
上面监听了点击事件的回调,这里有个问题,你并不能直接
onClick={this.handle('参数')}
传参,因为这样函数会立即执行,所以这种情况下似乎又回到了上面的箭头函数
和bind
。所以我认为这个class fields
适合用于不用传参
和props传递
的情况。对于props而言,是我们手动触发的。用于props的例子:
// 子组件 export default class HelloWorld extends React.Component { handle = () => { this.props.onUserChange({ name: 'gauhar chan' }) } render() { return (<div onClick={this.handle}>HelloWorld { this.props.user.name }</div>) } } // 父组件 export default class Game extends React.Component { constructor(props) { super(props) this.state = { user: { name: 'gauhar' } } } handleChangeUser = (user) => { this.setState({ user }) } render() { return <HelloWorld user={ this.state.user } onUserChange={this.handleChangeUser}/> } }
# 条件渲染
React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if
(opens new window) 或者条件运算符 (opens new window)去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
# && 运算符
这里就涉及到js
这个&&
的特性,如果表达式是true,那么会返回运算符右边的东西,否则会返回表达式本身的值。
render() {
const flag = false
return (
<div>
{ count && <h1>true</h1> }
</div>
)
}
上面渲染一个空div,这个是取决于什么数据类型,像数字0/NaN这种就会渲染
如果flag
是0,我们知道0会进行隐式转换条件false,但此时返回的是0本身(NaN同理)
render() {
const flag = 0
return (
<div>
{ count && <h1>true</h1> }
</div>
)
}
最终会渲染<div>0</div>
# 阻止组件渲染
在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render
方法直接返回 null
,而不进行任何渲染。
在组件的 render
方法中返回 null
并不会影响组件的生命周期。例如componentDidUpdate
依然会被调用。
# 表单
# 受控组件
就是通过state
去管理,将state
的值传入到组件的value
,监听onChange
事件通过setState
更新值
对比之下,
vue
的双向绑定确实很香
# 处理多个输入
当需要处理多个 input
元素时,我们可以给每个元素添加 name
属性,并让处理函数根据 event.target.name
的值选择要执行的操作。
export class IForm extends React.Component {
constructor(props) {
super(props)
this.state = {
phone: '',
password: ''
}
}
handleInputChange = (event) => {
const { name, value } = event.target
this.setState({
[name]: value
})
}
render() {
const { phone, password } = this.state
return (
<div>
<form>
<label>
手机号码:
<input
name="phone"
type="number"
value={phone}
onChange={this.handleInputChange} />
</label>
<br />
<label>
密码:
<input
name="password"
type="password"
value={password}
onChange={this.handleInputChange} />
</label>
</form>
<button onClick={ () => { console.log(this.state) } }>submit</button>
</div>
)
}
}
# 组件的包含关系
插槽
slot
到目前为止我发现了,
react
这是所有的东西都通过props
传递
在vue中,默认的插槽名称叫default
,react中则是props.children
如果要自定义名称,那么就通过props自定义
export function HelloFuntion(props) {
return (
<div>
HelloFuntion, { props.name }
<div>{ props.children }</div>
<div>{ props.diy }</div>
</div>
)
}
// ...
function DiyCom() {
return (
<div>我是自定义插槽</div>
)
}
<HelloFuntion name="gauhar" diy={ DiyCom() } >
<div>我是默认插槽</div>
</HelloFuntion>
# 代码分割
为了避免搞出大体积的代码包,在前期就思考该问题并对代码包进行分割是个不错的选择。 代码分割是由诸如 Webpack (opens new window),Rollup (opens new window) 和 Browserify(factor-bundle (opens new window))这类打包器支持的一项技术,能够创建多个包并在运行时动态加载。
对你的应用进行代码分割能够帮助你“懒加载”当前用户所需要的内容,能够显著地提高你的应用性能。尽管并没有减少应用整体的代码体积,但你可以避免加载用户永远不需要的代码,并在初始加载的时候减少所需加载的代码量。
# React.lazy
这么看来,
vue
确实有很重的react
影子呀这不就是
vue3
还在实验阶段的suspense
吗😂**(2022/05/05注)**
const OtherComponent = React.lazy(() => import('./OtherComponent'));
此代码将会在组件首次渲染时,自动导入包含 OtherComponent
组件的包。==说白了就是异步组件==
React.lazy
接受一个函数,这个函数需要动态调用 import()
。它必须返回一个 Promise
,该 Promise 需要 resolve 一个 default
export 的 React 组件。
然后应在 Suspense
组件中渲染 lazy 组件,如此使得我们可以使用在等待加载 lazy 组件时做优雅降级(如 loading 指示器等)。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
切换两个异步组件显示隐藏时,会出现一个情况就是。新的组件还没准备好,就被渲染出来了,因此用户会看到屏幕闪烁,这个时候可以使用 startTransition
(opens new window) API 来进行过渡。新的组件还没准备好时,保留显示旧的组件,新的准备好了,进行过渡切换
import React, { Suspense } from 'react';
import Tabs from './Tabs';
import Glimmer from './Glimmer';
const Comments = React.lazy(() => import('./Comments'));
const Photos = React.lazy(() => import('./Photos'));
function MyComponent() {
const [tab, setTab] = React.useState('photos');
function handleTabSelect(tab) {
startTransition(() => {
setTab(tab);
});
}
return (
<div>
<Tabs onTabSelect={handleTabSelect} />
<Suspense fallback={<Glimmer />}>
{tab === 'photos' ? <Photos /> : <Comments />}
</Suspense>
</div>
);
}
# 基于路由的代码分割
这里是一个例子,展示如何在你的应用中使用 React.lazy
和 React Router (opens new window) 这类的第三方库,来配置基于路由的代码分割。
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);