JSX
JSX 是 JS 的一种语法扩展,JSX 可以生成 React 元素。
// 实际上 Babel 会把 JSX 转译为 React.createElement() 函数调用
const element = (
<h1 className="greeting">
Hello React!
</h1>
);
// 这就是为什么你必须引入 React 库,因为使用 JSX 就需要 React 库
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello React!'
);
// React.createElement() 会创建这样的对象,也叫做 React 元素,就是虚拟 DOM
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello React!'
}
} 组件
组件名一定要大写,因为在 JSX 中小写的会被当成 html 标签
- 编译为 React.createElement('todo')
- 编译为 React.createElement(Todo);
React 的组件分为 函数组件 和 class 组件
// 函数组件没有内部的状态,也没有生命周期
function Hello(props) {
return (
<div>
// 函数组件使用 props
Hello React {props.name}
</div>
)
}
// class 组件则拥有状态、生命周期
class Count extends React.Component {
constructor(props) {
super(props);
}
render(){
return(
<div>
// class 组件使用 props
Hello React {this.props.name}
</div>
)
}
} 事件处理
传统的 HTML 使用纯小写 onclick,React 使用驼峰式 onClick
// html
<button onclick='func'></button>
// react
<button onClick={activateLasers}>
Activate Lasers
</button>
// 传统的 HTML 可以通过 return false 来阻止默认行为,React 不行,必须使用 event.preventDefault
<a href='#' onclick="console.log('The link was clicked'); return false">
Click me
</a>
// react
function ActionLink(){
function handleClick(e){
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href='#' onClick={handleClick}>
Click me
</a>
)
}
// React 中的 e 为合成事件,因此无需担心浏览器兼容性的问题。 组件生命周期
挂载
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
更新
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
卸载
componentWillUnmount()
setState
// 不要直接修改 state
// Wrong: 此代码不会重新渲染组件
this.state.comment = 'Hello';
// 应该使用 setState
// Correct
this.setState({
comment: 'hello'
})// state 的更新可能/通常是异步的
class App extends React.Component {
state = {count: 0}
...(){
this.setState({count: 1});
console.log(this.state.count) // 输出 0
}
}
// 如果你想要获得修改后的值
this.setState({
count: 1
}, () => {
console.log(this.state.count) // 输出 1
})// 如果第二个 setState 依赖于第一个 setState 之后的值,则可以将第二个 setState 的参数设置为函数
this.setState({
count: 1
}, () => {
console.log(this.state.count);
})
this.setState((state, props) => {
return {
count: state.count + 1;
}
})setState 何时是异步的
在合成事件和组件的生命周期中 setState 是异步的,在原生事件和定时器中 setState 是同步的。
React 内部维护了一个标识 isBatchingUpdates,当这个值为 true 表示将 setState 缓存进入队列,最后进行批量更新;当这个值为 false 时表示直接更新。
合成事件和组件的生命周期中,会把 isBatchingUpdates 设置为 true;
原生事件和定时器中,会把 isBatchingUpdates 设置为 false;
当你调用 setState() 的时候, React 会把你提供的对象合并到当前的 state;
列表渲染
{
props.todos.map((todo) => {
return
})
}
表单
class App extends React.Component{
constructor(props){
super(props);
this.state = {
value: ''
}
}
handleChange = (e) => {
this.setState({
value: e.target.value
})
}
render(){
return (
<div>
{this.state.value}
<input type="text" value={this.state.value} onChange={this.handleChange} />
</div>
)
}}
受控组件
通常表单内部拥有自己的状态,状态会被用户的输入所改变。而 React 中,用户的输入会被劫持,实际上的数据源完全由 React 提供
React 的 state 称为组件/表单的唯一数据源,渲染表单的组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。当然,与之对应的称为“非受控组件”。
Refs
何时使用 Refs
- 管理焦点,文本选择或媒体交换
- 继承第三方 DOM 库
- 触发强制动画
class App extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
handleClick = () => {
this.myRef.current.focus();
}
render() {
return (
<div>
// 绑定
<input type="text" ref={this.myRef} />
<button onClick={this.handleClick}>点我</button>
</div>
)
}
}组件通信
父子组件通信
父组件通过 props 传递数据给子组件。
父组件通过 props 把自己的函数传递给子组件,子组件内部可以直接调用,实现子组件向父组件通信。
非父子组件通信
可以通过 events 实现发布-订阅,也可以借助于 context。
复杂的情况可以考虑用 Redux。
Context
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
// Context 让我们可以无需明确地传遍每一个组件,就能将值深入传递进组件树
// 为当前地 theme 创建一个 context
const ThemeContext = React.createContext('light');
class App extends React.Component {
render(){
// 使用一个 Provider 来将当前地 theme 传递给以下地组件树
// 无论多深,任何组件都能读取这个值
// 在这个例子中,我们将 “dark” 作为当前的值传递下去
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了
function Toolbar(props){
return (
<div>
<ThemeButton />
</div>
);
}
class ThemeButton extends Component {
// 指定 contentType 读取当前的 theme context
// React 会向上找到最近的 theme Provider,然后使用它的值
// 在这个例子中,当前的 theme 值为 “dark”
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}Fiber 架构
React 在它的 V16 版本推出了 Fiber 架构。
浏览器是多线程的,这些线程包括 JS 引擎线程(主线程),以及 GUI 渲染线程,定时器线程,事件线程等工作线程。其中,JS 引擎线程和 GUI 渲染线程是互斥的。又因为绝大多数的浏览器页面的刷新频率取决于显示器的刷新频率,即每16.6毫秒就会通过 GUI 渲染引擎刷新一次。所以,如果 JS 引擎线程一次性执行了一个长时间(大于16.6毫秒)的同步任务,就可能出现掉帧的现象,影响用户的体验。
在旧版本的 React 中,对于一个庞大的组件,无论组件的创建还是更新都可能需要较长的时间。而 Fiber 的思路是将原本耗时较长的同步任务分片为多个任务单元,执行完一个任务单元后可以保存当前的状态,切换到 GUI 渲染线程去刷新页面,接下来再回到主线程并从上个断点继续执行任务。
React 中的 Fiber,将原本耗时很长的同步任务分成多个耗时短的分片,从而实现了浏览器中互斥的主线程与 GUI 渲染线程之间的调度。
除此之外,对于每一个 Fiber 的同步任务来说,都拥有一个优先级(总共定义了6中优先级)。
当主线程执行完一个任务 A 的一个分片,若此时出现了一个优先级更高的任务 B, React 就可能会把任务 A 废弃掉,待之后重新执行一次任务 A。
Diff 策略
- Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
- 对于同一层级的一组子节点,它们可以通过唯一 ID 进行区分。
Hook
Hook 是一种特殊的函数,它可以让你“勾入” React 的特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 class 组件的特性。
useState
import React, { useState } from 'react';
function Example(props) {
let [count, setCount] = useState(0);
return (
<div>
{ count }
<button onClick={() => setCount(count + 1)}>
Click Me
</button>
</div>
)
} useEffect
Effect Hook 可以让你在函数组件中执行副作用操作
import React, {
useState,
useEffect,
} from 'react';
function App(){
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `YOU clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count+1)}>
Click Me
</button>
</div>
);
} useEffect 会在每次渲染后都执行,包括初次渲染和每次数据更新之后。
组件挂载时,运行副作用;组件更新时,先清除上一个 effect,再运行下一个 effect;组件卸载时,清除最后一个 effect;
function FriendStatus(props){
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
}
})
} useRef
import React, { useRef } from 'react';
function App(props){
let refs = useRef(null);
return (
<input ref = {refs}>
)
} useReducer
function useReducer(reducer, initialState){
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
function Todos() {
const [todos, dispatch] = useReducer(todosReducer, []);
function handleAddClick(text) {
dispatch({ type: 'ADD', text});
}
// ...
}
function todosReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, {
text: action.text,
completed: false,
}];
// other actions
default:
return state;
}
} useMemo 和 useCallback
Vue 和 React 有一个比较明显的差异。
由于 Vue 使用了对数据的劫持,知道具体数据的变化。因此当父组件数据改变,而子组件数据没有改变时,只有父组件会重新渲染。
而 React 的方式很粗暴,只要父组件的数据改变,强制会让子组件也重新渲染。
React 的类组件分为 React.Component 和 React.PureComponent, React 会浅对比该组件的前后 state 和 props,如果没有变化,则不会重新渲染组件。
而对于函数组件来说,默认情况下也是会重新渲染的,我们可以通过 React.memo 包裹函数组件来实现类似 PureComponent 的效果。
总的来说,类组件我们可以使用 PureComponent,函数组件我们可以使用 React.memo 来提高性能。
useMemo
// 返回一个 memoized 值 const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把“创建”函数和依赖数组项作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
import React, {useState, useEffect} from 'react';
const Child = (props) => {
useEffect(() => {
console.log("子组件渲染");
});
return (
<div>
<div>子组件</div>
<button onClick={props.onClick}>子组件按钮</button>
</div>
)
}
const MemoChild = React.memo(Child);
const Father = () => {
const [count, setCount] = useState(0);
return (
<div>
<span>父组件</span>
<span>计时器:{count}</span>
<button onClick={() => setCount(count + 1)}>父组件按钮</button>
<MemoChild onClick={() => setCount(count + 1)}/>
</div>
)
} 上面的代码,即使我们使用了 React.memo()包住子组件,当父组件的 count 数据变化时,子组件也会重新渲染。
问题出在:
<MemoChild onClick={() => setCount(count + 1)} /> 每次父组件重现渲染时,传给子组件的 props 的地址就发生了变化(也就是说只要是引用类型,都会存在这个问题),因此子组件也会重新渲染。
在类组件中,组件的重新渲染不会影响函数的地址,因此不会影响子组件。
所以我们需要在函数组件中保存某个函数的地址
const cb = useCallback(() => {
setCount(count => count + 1)
}, [])
<MemoChild onClick={cb} /> React-Router
import React from 'react';
import {
BrowserRouter as Router,
Link,
Switch,
Route
} from 'react-router-dom'; // web 端调用 react-router-dom
function App(){
return (
<Router>
<Link to="/">首页</Link>
<Link to="/blog">博客</Link>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
)
} 动态路由匹配
import {
...
useParams,
} from 'react-router-dom'
<Switch>
<Route path='/user/:id'>
<User />
</Route>
</Switch>
function User(){
let { id } = useParams();
return (
<div>
user: { id }
</div>
);
}React 和 Vue 的对比
共同点
- 都使用了 Virtual DOM
- 都提供了响应式和组件化的视图组件
- 将注意力集中保持在核心库,其他功能如路由和全局状态管理交给相关的库
不同
- 优化: React 应用中,某个组件的状态发生改变时,它会以组件为根,重新渲染整个组件子树。
如果要避免不必要的子组件的渲染,需要使用 PureComponent 或者 shouldComponentUpdate 方法进行优化- 在 Vue 应用中,组件的依赖是在渲染过程中自动跟踪的,所以系统能精确知道哪个组件需要被重新渲染
虚拟 DOM
虚拟 DOM 只是一个单纯的 JS 对象。
虚拟 DOM 的优劣
使用虚拟 DOM:当我们修改我们的数据重新生成新的虚拟 DOM,新老虚拟 DOM 进行 DIFF 操作之后,框架底层内部会对我们的真实 DOM 进行操作。
使用真实 DOM:直接手动操作 DOM
优点
- 保证性能的下限
当我们使用虚拟 DOM 时,框架会帮我们完成 DOM 的操作,相当于是一个自动化的过程。- 跨平台
虚拟 DOM本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
缺点
虚拟 DOM 的使用可以保证性能的下限,但也正是因为如此,它也无法做到极致的优化。毕竟我们操作虚拟 DOM 的最终目的是操作真实 DO,那论性能的上限自然是无法与直接操作真实 DOM 相比。

京公网安备 11010502036488号