你要尽全力保护你的梦想。那些嘲笑你梦想的人,因为他们必定会失败,他们想把你变成和他们一样的人。我坚信,只要我心中有梦想,我就会与众不同,你也是。 —— 《当幸福来敲门》
基础
React
一些入门知识。用来让读者对 React
有一定的认识
生命周期(React Version >= 16.4)
生命周期钩子列表
-
constructor()
-
render()
-
componentDidMount()
-
componentDidUpdate()
-
componentWillUnmount()
-
getSnapshotBeforeUpdate()
-
shouldComponentUpdate()
React
的生命周期一共有三个阶段,分别为 挂载阶段
、更新阶段
和 卸载阶段
挂载阶段
-
constructor()
-
render()
-
componentDidMount()
constructor()
- 用于初始化内部状态
- 唯一可以直接修改state的地方
componentDidMount()
- 只执行一次
- UI渲染完成后调用(类似于
Vue
的 mounted
)
render()
- 渲染视图
- 必须存在的
更新阶段
-
shouldComponentUpdate()
—— 强制更新 ( 使用 this.forceUpdate()
) 会跳过这个钩子 -
render()
-
componentDidUpdate()
shouldComponentUpdate()
- 决定虚拟DOM是否要重绘,当返回值为
true
时会选择重绘 - 一般可以由PureComponent自动实现
- 常用于性能优化
componentDidUpdate()
- 每次UI更新时被调用
- 典型场景:页面需要根据
props
变化重新获取数据
卸载阶段
-
componentWillUnmount()
componentWillUnmount()
- 组件移除时调用 ( 类似于
Vue
的 beforeDestroy
) - 常用于释放资源 ( 例如清除定时器 )
state状态相关方法
state
是 React
里面存储状态的对象,相当于 Vue
的 data
-
setState()
-
forceUpdate()
setState()
setState(updater, [callback])
setState
可接受两个参数,第二个是可选的。
使用 setState
设置 state
有两种方式
-
setState
传入对象或者函数对象,直接修改 state
中的数据 -
setState
中传入两个函数。第一个函数有两个参数,第一个参数是当前的 state
,第二个参数是当前的 props
,返回的是要修改的state
对象,类似于第一种设置方式;第二个函数是更新后的回调函数。
注意事项
官方建议用 componentDidUpdate()
代替更新后的回调函数,其中一个原因是 React
为了考虑最佳性能,会在所有 setState
全部执行完之后进行统一的渲染。
如果你同时使用了两次 setState
修改同一个 state
值,会存在一些隐患。看下面这个例子
class App extends React.Component {
constructor(props) {
super(props);
this.state = { value: 0 };
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate: ' + this.state.value);
}
onClick = () => {
this.setState(
{ value: 4 },
() => console.log('onClick: ' + this.state.value));
this.setState(
{ value: 8 },
() => console.log('onClick: ' + this.state.value));
}
render() {
return <button onClick={this.onClick}>{this.state.value}</button>;
}
}
这样的代码将会打印两次 onClick: 8
。因为这两个 setState
是同时处理的。
forceUpdate()
component.forceUpdate(callback) // 强制渲染组件
默认情况下,当组件的
state
或 props
发生变化时,组件将重新渲染。如果 render()
方法依赖于其他数据,则可以调用 forceUpdate()
强制让组件重新渲染。
虚拟DOM和key属性
React
使用 虚拟DOM
的方式优化性能。
当React
组件的状态发生变化时,React
不会直接更新视图,而是创建一个新的虚拟DOM
缓存所有发生变化的状态,然后等到合适的时候 一次性 更新到浏览器。
当React
组件重新渲染时,React
不会渲染整个视图,而是使用 Diff
算法 比较新旧虚拟DOM
之间的差异,然后只把 真正发生变化的地方 更新到浏览器。
虚拟DOM的两个假设
- 组件的
DOM
结构是相对稳定的 - 类型相同的兄弟节点可以被唯一标识
key
属性是用来帮助 React
识别那些元素改变了,从而更好的使用虚拟DOM
提高性能,所以它最好是唯一的。
无状态组件和类组件
无状态组件又叫 纯函数组件 ,是没有 state
的组件,通常用来定义可复用的模板。
以下是一个例子。
const Component = (props) = (
<div>{props.xxx}</div>
);
类组件又称有状态组件,它是有 state
的,通常用来定义交互逻辑和业务数据。
以下是一个例子
class Home extends React.Component {
constructor(props) {
super(props);
};
render() {
return (
<Header/>
)
}
}
HOC(高阶组件)
高阶组件(HOC)是
React
中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API
的一部分,它是一种基于 React
的组合特性而形成的设计模式。
具体而言,高阶组件是参数为组件,返回值为新组件的函数。
HOC
是用来将一个组件转换为另外一个组件的。
Context
Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据。可以把 Context
理解为注入数据的工具(类似于 Vue
的 provider
) 。
Context
提供了一个无需为每层组件手动添加 props
,就能在组件树间进行数据传递的方法。
我们通常在需要修改某个全局类型时使用 Context
,例如我们需要实现 换肤 效果。
官方示例
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
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() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
进阶
React
一些主流的开发技巧。
Hook
Hook
是 React 16.8
的新增特性。它可以让你在不编写 class
的情况下使用 state
以及其他的 React
特性。
Hook
是 React16.8
之后主流的开发利器。Hook
主要实现于函数组件或者自定义的Hook
函数。
useState()
我们通常使用 useState
来设置 state
const [state, setState] = useState(initialState);
以下使用 useState
的例子
const [loading, setLoading] = useState(false);
useState
返回一个数组,数组的第一个成员是 state
状态,第二个成员是设置 state
状态的函数。
useEffect()
useEffect(callback,[count])
useEffect
做了什么? 通过使用这个 Hook
,你可以告诉 React
组件需要在渲染后执行某些操作。React
会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM
更新之后调用它。
这个 hook
主要是用来操作副作用的。它跟 class 组件中的 componentDidMount()
、componentDidUpdate()
和 componentWillUnmount()
具有相同的用途。
useEffect()
有两个参数,第一个参数( 是个函数 )是用来操作副作用的,第二个参数( 是个数组、可选 )是用来约束在哪些数据更新时需要执行。
第二个参数的效果
参数情况 | 效果 | 注意事项 |
不传 | 每次渲染后都执行清理或者执行effect | 这可能会导致性能问题,比如两次渲染的数据完全一样 |
传递空数组 | 只运行一次的 effect(仅在组件挂载和卸载时执行) | 这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行 |
传递 [ count ] | React 将对前一次渲染的count和后一次渲染的count进行比较。若相等React 会跳过这个 effect, | 实现了性能的优化 |
如果给第二个参数传递指定的值,可以实现一定的性能优化。具体根据实际情况来选择。它( 第二个参数 )可以控制setEffect
的执行频率。
副作用
副作用指的是在完成某件事时附带执行的事。在 useEffect
的基本概念中,主要的事是 DOM
构建,其余的就是副作用。
useContext()
const value = useContext(MyContext);
接收一个
context
对象(React.createContext
的返回值)并返回该 context
的当前值。当前的 context
值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
prop
决定。
通常分两步实现使用 context
const ThemeContext = React.createContext(themes.light);
const theme = useContext(ThemeContext);
官网示例
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
// 会得到color: "#ffffff";background: "#222222";
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}