文章涉及到的在线事例
用密:admin/admin
虚拟 DOM
标签就是函数,标签的属性就是函数的参数
<Parent name={this.state.name}/>
我们自定义组件的时候必须要大写开头,因为只有这样 createElement
把组件当成一个变量而不是字符串。
组件名不能是表达式,应该是以大写字母开头的变量。
在 react
中一切皆可 props
包括基本数据类型,React 元素以及函数。
props
的默认值为 true
// 二者等价
jsx
可以进行字符串字面量转移,防止 xss
(跨站脚本攻击) 。
react
中有三种创建更新的方式
通过 ReactDom.render()
|| hydrate
初次渲染
通过 setState
更新
通过 forceState
更新
<div class='container'>
parent
<p><span>span</span></p>
<div>div</div>
</div>
// 转换为 每一个节点都需要 React.createElement 进行创建
React.createElement("div", {
class: "container"
}, "parent",
React.createElement("p", null,React.createElement("span", null, "span")),
React.createElement("div", null, "div"));
先创建一个 ReactRoot
顶点对象
app.js
// 根据给 createElement 传了一个 APP 类
ReactDOM.render(<App />, document.getElementById('root'));
// 源码 ReactDOM.js
function ReactRoot(
container: DOMContainer,
isConcurrent: boolean,
hydrate: boolean,
) {
// 这个 root 指的是 FiberRoot
// createContainer 平台、任务调度的东西
// createContainer => return createFiberRoot(containerInfo, isConcurrent, hydrate);
const root = createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}
到这里就创建了一个 root 对象,react 在创建对象的时候会把根节点内的所有元素都移除。我们一般都写成 没有子元素
再创建 FiberRoot
和 RootFiber
,二者是非常重要的 api
fiber
和 Fiber 是两个不一样的东西,前者代表着数据结构,后者代表着新的架构。dom
节点对应着一个 fiber
对象FiberRoot
就是整个 fiber
树的根节点function ReactRoot(
container: DOMContainer,
isConcurrent: boolean,
hydrate: boolean,
) {
// 这个 root 指的是 FiberRoot 这里创建了一个 FiberRoot
const root = createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}
)// react fiberRoot 中的数据结构
type BaseFiberRootProperties = {
// 容器,也就是 render 的第二个参数
containerInfo: any,
// Used only by persistent updates.
// 只在持续更新中使用
pendingChildren: any,
// The currently active root fiber. This is the mutable root of the tree.
// 当前的 fiber 对象,也就是 root fiber
current: Fiber, // 指向 RootFiber
// 用来存放 update,也就是用来记录改变状态的
updateQueue: UpdateQueue <any> | null,
...
}
fiber
会组成一个树结构,内部使用了单链表树结构,每个节点及组件都会对应一个fiber
sibling
),还有一个 return
指回了父节点。Dom
结构如下updateContainer
currentTime
代表 react
初始化的时间expirationTime
代表更新的优先级
expirationTime
大于当前时间 react
就会延迟执行这个任务
sync
的数字是最大的,所以优先级也是最高的ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
// 这里指 FiberRoot
// document.querySelector('#root')._reactRootContainer 可进行查看
const root = this._internalRoot;
// ReactWork 的功能就是为了在组件渲染或更新后把所有传入
// ReactDom.render 中的回调函数全部执行一遍
const work = new ReactWork();
callback = callback === undefined ? null : callback;
// 如果有 callback,就 push 进 work 中的数组
if (callback !== null) {
work.then(callback);
}
// work._onCommit 就是用于执行所有回调函数的,就是我们传进来的 callback
// updateContainer( ,'root节点',parentComponent,callback)
// 这里的 parentComponent react采用了硬编码直接为 null
updateContainer(children, root, null, work._onCommit);
return work;
};
// 非常重要的 api
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
// 取出容器的 fiber 对象,也就是 fiber root
const current = container.current;
// 计算时间 (react 初始化用了多少时间)
const currentTime = requestCurrentTime();
// expirationTime 代表优先级,数字越大优先级越高(指的是一个任务的过期时间)
// sync 的数字是最大的,所以优先级也是最高的
// 交互事件 优先级较高
// 异步事件 优先级最低
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
export function createUpdate(expirationTime: ExpirationTime): Update<*> {
// 这里指的是 React.render(payload,callback)
return {
expirationTime: expirationTime,
tag: UpdateState,
// setState 的第一二个参数
payload: null,
callback: null,
// 用于在队列中找到下一个节点
next: null,
nextEffect: null,
};
}
updateQueue
中// 进入任务调度 有更新产生 需要更新
scheduleWork(current,expirationTime)
state 和 props 之间最重要的区别是:props 由父组件传入,而 state 由组件本身管理。组件不能修改 props,但它可以修改 state。
构造函数是唯一可以给 this.state 赋值的地方
父传子
子传父
// 父组件
class Parent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
test(){
const childMethodResult = this.myRef.current.xxx() // 'child methods'
}
render() {
return <Children ref={this.myRef} />;
//return this.myRef = el} />; 推荐这样创建 ref
}
}
// 子组件
class Child extends React.Component {
constructor(props) {
super(props);
}
xxx(){
return 'child methods'
}
render() {
return null;
}
}
setState()
调用合并成一个调用,state
的更新可能是异步的。props
的更新是异步的,因为re-render
父组件的时候,传入子组件的props
才变化;为了保持数据一致,state
也不直接更新,都是在flush
的时候更新。setState()
接收一个函数而不是一个对象。这个函数用上一个state
作为第一个参数,将此次更新被应用时的 props
做为第二个参数: this.setState((state, props) => ({
counter: state.counter + props.increment
}));
react
的生命周期钩子函数中是异步的定时器中
元素的 DOM
事件( ref
获取到原生的 dom
)
Promise
(下面的 data 是实时更新的)
Promise.resolve().then(data=>{
console.log(this.state.data)
this.setState({data})
console.log(this.state.data)
}
用 props
传入数据的话,组件可以被认为是受控。受控标签都接受一个 value
属性
input
输入框,由 react 控制表单的输入。state
成为唯一的数据源this.setState({
value: event.target.value
})
render(){
return (
<input type="text" value={this.state.value} onChange={this.handleChange} />
)
}
数据只保存在组件内部 state
上的是非受控组件(因为外部没办法直接控制 state
)。
input
它的 value
只读,所以它是 React 中的一个非受控组件<input type="file" />
Dom
来处理
采用广度优先,层层比较的方式,算法时间复杂度 o(n)。
情况一:A B 节点的属性、位置发生了变化,进行位置交换。
情况二:节点类型发生变化会直删除节点,重新渲染新节点。
React 为什么要这样进行 DOM diff
它优化了触发浏览器 reflow
和 repaint
的步骤,把众多页面节点改动集中到一次来进行触发。在用 setState
顺利触发了component
的 render
后,react
会对 Virtual DOM
进行操作,而这些操作并不会触发浏览器的 reflow
和 repaint
,因为 Virtual DOM
它只是存在内存中的一个有着 DOM
层级关系的数据而已。 当最终形成了新的 Virtual DOM
后,转换成真实的 DOM
这一步才会触发浏览器的 reflow
和 repaint
。
它是一种设计模式,不是 React 独有。
普通组件是将 props 转换为 UI
高阶组件是将组件转换为另一个组件
props
适应场景
例如一个时钟的显示,封装成一个高阶函数组件 进行导出,然后在组件 B 中使用。
// 高阶组件 A
export default function A (WrapComponent){
return class extends Component {
state = {time:new Date()}
}
render(){
return (
<WrapComponent time={this.state.time} ...this.props />
)
}
}
// 组件 B
import A from 'xxx'
class B extends Component {
render(){
return (
<div>{this.props.time}</div>
)
}
}
export default A(B) // 这样就可以获得 A 组件的属性
上面可以看到,并没有复用 A 组件。
出现的意义是为了解决组件间通信的问题,因为组件间数据的层层传递非常麻烦。Redux
就依赖此 API
。来共享全局状态。
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
// 如果有其它的 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>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
render(){
return (
<ThemeContext.Consumer>
{/* 以函数作为子组件,放到 Consumer 里面才会生效 */}
{
theme => <Button theme={theme} {...props}/> // theme === 'dark'
}
</ThemeContext.Consumer>
)
}
}
如果增加一些功能,点击切换主题,道理是一样的,切换的时候传给 Context
的 Provider
传入主题的值让其层层传递就可以。这样就做到了实时更新数据。
为什么不采用写一个外部配置文件的方式进行主题更改?
如果写成一个配置文件去切换,那么你还需要监听数据的变化然后再进行更新(forceUpdate
)的操作。因为外部的数据并不属于组件内部的一个状态。
项目只有一个 createContext
的情况下可以采用简写的形式,上面中我们获取 provider
提供的 value
需要在 render
中的函数里得到,利用 contextType
我们可以不写函数也能得到
class ThemedButton extends React.Component {
static contextType = ThemeContext; // 静态方法
render(){
const theme = this.context // 调用 context 直接返回
return (
<Button theme={theme} {...props}/> // theme === 'dark'
)
}
}
Redux 的原则 :
应⽤中所有的 state
都以⼀个对象树的形式储存在⼀个单⼀的 store
中。唯一改变 state
的办法是触发 action
⼀个描述发⽣了什么的对象。
为了描述 action
如何改变 state
树,你需要编写 reducers。
React 的模式是 state 到 DOM,是组件内部的状态。
Redux 的模式是把状态移动到了组件之外,全局的状态,放到一个唯一的 store 树上。
Redux 的特性是纯函数更新 store,reducer 根据传入的 state 和 action 生成新的 state 。
reducer
函数,返回 store
。这样就产生了一个 store 。store
的方法
getState()
返回值为 store
内部保存的数据dispatch()
参数为 action
对象,触发 reducer
,更新 store
触发 subscribe
监听,更新视图subscribe()
参数为监听内部 state
更新的回调函数actionCreators
(Function or Object): 一个 action creator,或者一个 value 是 action creator 的对象。dispatch
(Function): 一个由 Store
实例提供的 dispatch
函数。// action
function plus(){
return { type:'PLUS',payload:{count:1} }
}
// 用store 分发一个 action
store.dispatch(plus())
用 bindActionCreators 进行改写
// action
function plus(){
return { type:'PLUS',payload:{count:1} }
}
function minus(){
return { type:'MINUS',payload:{count:1} }
}
const action = bindActionCreators({plus,minus},store.dispatch)
// 内部会执行 dispatch
action.plus()
action.minus()
流程说明:
在 View 进行 click 触发一个异步 action 然后被中间件(redux-thunk)截获,处理完成后,进行dispatch
到reducer
然后到到 state
进行处理,更新 state
触发 View 更新。
中间件是一些函数用于定制对特定请求的处理过程
function middleWare({dispatch,getState}){
return function(next){
return function(action){
//do...
return next(action)//处理完后调用下一个中间件
}
}
}
// es6写法
({dispatch,getState})=>next=>action=>next(action)
// store
import {createStore,applyMiddleware} from 'redux'
import ReduxThunk from 'redux-thunk'
export default createStore(appReducer,(applyMiddleware(ReduxThunk)))
redux-thunk
截获异步请求的依据就是判断这个 action
是不是一个函数 (Promise
)是的话就执行这个函数,执行后再 diapatch
这个 action
reducer
是不允许有副作用的。你不能在里面操作 DOM
,也不能发 Ajax
请求,更不能直接修改 state
,它要做的仅仅是 —— 初始化和计算新的 state
。就是根据老的 state
和传入的 action
生成一个新的 state
。一般在一个项目中会有好多个 reducer
函数。
例如:一个获取登录用户信息的 reducer
export function loginUserInfo (previousState = {}, action) {
if (action.type === GET_LOGIN_USER_INFO || action.type === LOGIN) {
return action.data.userInfo || {}
} else if (action.type === LOGOUT) {
return {}
} else {
return previousState
}
}
n
个 reducer
的对象,返回一个新的 reducer
函数connect() 是一个高阶函数,执行后返回一个高阶组件,接收一个UI组件,返回一个容器组件。
从左到右看。
参数为:
mapStateToProps:
一个函数,指定向 UI 组件传递哪些一般属性,必须返回一个对象。mapDispatchToProps:
可以是对象,对象所定义的方法名将作为属性名;也可以是函数通过dispatch
显式分发action
,每个方法将返回一个新的带有(dispatch,getState)
参数的函数,目的是向UI组件传递方法。Provider
组件,接收 store
属性让所有组件都看到 store
,通过 store
读取、更新状态。
export default App extends Component {
render(){
return (
<Provider store = { store } >
</Provider>
)
}
}
action
和 reducer
放在同一个文件,利于改写不用来回切换文件。action.js
和 reducer.js
导入所有的 action
和 reducer
actionType
常量采用下划线命名法action、reducer
采用驼峰命名法,适当可使用下划线actionType
名称与 action
的名称,结构都动宾结构:‘V+N’
reducer
方法的命名为action
名去掉动词部分的宾语(名词)部分以获取登录用户信息为例子:
// actionType常量
GET_LOGIN_USER_INFO = 'GET_LOGIN_USER_INFO'
// action方法
export function getLoginUserInfo (params) {
return async (dispatch,getState)=>{
const res = await reqLogin(loginInfo)
if (res.status === 0) {
// 分发一个同步action
dispatch(
{
type:LOGIN_USER_INFO,
data:res.data
}
)
}
}
}
//reducer
export function loginUserInfo (previousState = {}, action) {
if (action.type === GET_LOGIN_USER_INFO || action.type === LOGIN) {
return action.data.userInfo || {}
} else if (action.type === LOGOUT) {
return {}
} else {
return previousState
}
}
如上图:我们更新左边图的一个点,无论你怎么更新都需要复制一份(深、浅拷贝)包含修改的部分,右图绘制绿色的点需要更新的点,其它没有改变的点无需更新。没有改变的点就是不可变数据。
reducer
根据传入的 state
和 action
生成了一个新的 state
而不是修改原有的 state
。state
而不需要比较值,继而不需要进行深层次的遍历,只比较它们的引用就可以,从而确定是否需要更新组件。state
。action
的状态 ,是否正确。原生的两种方法,Object.assign()
和 {...}
。
Immutability-helper
(节点很深可以用这个,用法类似 Object.assign()
)
Immer
Immer 的用法,性能没有原生的Object.assign()
好。
import produce from 'immer'
// 接收旧的 state ,回调函数里可以直观的进行修改,类似在原有的 state 上进行修改,采用代理的方式。
produce(state,drafState=>{
drafState.todos.push('xxx')
})
URL
可以定位到页面URL 模式
BrowserRouter
实现,HTML5 中 history
API 。 <BrowserRouter>
<Switch>{/* 该组件只渲染第一个与当前访问地址匹配的 或 */ }
<Route exact path={'/login'} component={Login}></Route>
<Route path={'/'} component={Admin}></Route>
</Switch>
</BrowserRouter>
hash 模式
HashRouter
实现内存模式
MemoryRouter
实现。通过 URL 传递参数
<Link to = {'/user/123'}> render user123 </Link>
<Router path = {'/user:id'} .../>
如何获取参数
{/* 获取上面的 id */}
this.props.match.params.id
Facebook 开源的 JS 单元测试框架
…
易于开发
易于维护
易于构建
易于测试
易于扩展
组件拆分
根据 React
的虚拟 DOM DIFF 算法 O(n)
。组件根据情况拆分的越细,React
性能越高。因为组件细了后就可以作为一个纯组件对待,可以整体看成是一个 dom
,组件状态未发生改变的时候,就不会更新这个 dom
节点,对应的也就不会进行 Dom diff 的操作。
网络优化,按需加载
借助 webpack import
进行动态加载。
使用 react-loadable库。
/**
* @desc 借助 react-loadable 进行 code-splitting 时的loading组件
*/
function Loading({ error }) {
if (error) {
return 'error';
} else {
return <div></div>;
}
}
/**
* @desc 引入,注意是import()方法,不是import关键字
*/
import Loadable from 'react-loadable'
const Home = Loadable({
loader: () => import('./children/Home'),
loading: Loading,
})
/**
* @desc 路由匹配成功的时候才引入,而不是一开始全部引入
*/
<Route path="/home" exact component={Home} />
React 的 Suspense
api
借助 webpack 、 import 动态导入。
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
componentDidCatch
,进行 render
错误的渲染。总结:
pureComponent
,提供简单的对比算法比较新旧 props state 减少性能开销。pureComponent
只能直接对比值,值的内部发生改变无法对比。state
可以借助 memo
达到同样的效果。它们都是使用 Object.is(new,old)
进行对比。和 ===
区别的
Object.is(+0,-0) // false
Object.is(Number.NaN,NaN) // true
+0===-0 // true
NaN===NaN // false
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
状态逻辑难以复用
render
、props
或者高阶组件(HOC)都在外层包裹了一层组件,无端增加代码层级。class
组件就会难以维护。事件的绑定解绑在不同的生命周期
componentDidMount
中注册事件以及其他的逻辑,在 componentWillUnmount
中卸载事件。this
指向问题
构造函数显示 bind(this)
render
函数显式 bind(this)
箭头函数绑定
静态类属性
handleClick = () => {
this.setState({
num: this.state.num + 1,
})
};
render() {
return (<div>
<button onClick={this.handleClick}>click me</button>
</div>)
}
涉及代码
在线事例
用密:admin/admin
解决了上述的几类问题。
可以复用状态逻辑(自定义 Hooks)。
新的 api
使用范围广。
副作用关注点分离
useEffet
对应一个副作用,不用像 class
那样都写到对应的生命周期函数中去。每一个 Hooks 相互独立
只能在 React 的函数组件中调用 Hook
必须把 Hooks 写在函数的最外层不能写在 if...else
等条件语句当中
就是靠这样确保每次调用次序的一致性,而保证多个 useState
是相互独立的。
useState
是相互独立的不然就会打乱useState
的调用次序
state
,返回一个数组。state
第二项是操作 state
的方法setState
this.setState
的区别是 setState
是直接用新的 state
替换旧的state
。而 this.setState
是拿新的 state
和旧的 state
进行合并。useState
就像我们定义 this.state
那样有多个状态。相应的每个状态对应一个改变它的 setState
。当你觉得现在的 state 满足不了需求的时候不防增加一个 state 变量试试
点击按钮 count 就会自动更新,如果 count 的值没有变化就不会进行重新渲染。
setState
使用 Object.is(oldState,newState)
进行判断新旧值是否相等
和’=‘的区别是’='将数字值 -0
和 +0
视为相等,并认为 Number.NaN
不等于 NaN
。
function HooksUseState() {
// 数组的解构 参数0是state的初始值 返回的变量
// 等价于 const _useState = useState(0)
// 等价于 const count = _useState[0]
// 等价于 const setCount = _useState[1]
const [count, setCount] = useState(0);
return (
<div>
<h1>{count}</h1>
<button onClick={()=>setCount(count+1)}>+</button>
</div>
);
}
合并更新对象的多个属性
function Counter() {
const [counter, setCounter] = useState({
name: "计数器",
number: 0,
count: 0
});
return (
<>
<p>
{counter.name}number:{counter.number}count:{counter.count}
</p>
<button
onClick={() =>
setCounter({
...counter,
number: counter.number + 1,
count: counter.count + 1
})
}
>
+
</button>
</>
);
}
useState
state
就会强制渲染当前的 Hooks 组件。Class
有 pureComponent
,函数组件有 React.memo
,它们通过比较新旧 state
来减少组件的渲染次数。那么 useState
该如何做呢?
上面我们已经说了如果 useState
接收到的属性不变,则不会重新渲染函数。但是 useState
每次都会重新执行,接受到的 state
尽管是同一个它还是会把它当成新的 state
解决办法:
// 给初始值传入一个函数来执行 fn 这样 fn 就执行一次,否则直接传入 fn() 每次更新都会执行 fn
useState(()=> fn())
用来处理副作用的操作,例如:事件绑定与解绑、发送网络请求、操作DOM。
useEffect
相当于 componentDidMount
和 componentDidUpdate
、componentWillUnmount
适用于组件挂载前、挂载后、更新后。如需清除上一次的副作用,可返回一个函数,函数内部执行清除副作用的操作。
let timer;
function ajax() {
return new Promise((resolve, reject) => {
timer = setTimeout(() => {
resolve("我是后台返回的内容");
}, 2000);
});
}
function Hooks() {
const [content, setContent] = useState("loading");
// 异步请求 ,清除 timeout
useEffect(() => {
ajax().then(data => {
setContent(data);
});
// 清除定时器
return function clear() {
if (timer) {
clearTimeout(timer);
}
};
});
return (
<div>
<div style={{ padding: "30px" }}>
<Row gutter={16}>
<Col span={8}>
<Card title="useEffect" >
2s 后从后台获取 content:
{content}
</Card>
</Col>
</Row>
</div>
</div>
);
}
export default Hooks;
第二个参数是一个数组,只有数组的每一项都不变才会阻止 useEffect
的调用。
为什么要用第二个参数?
因为其它函数引起 Hooks 函数渲染的的时候 useEffect
每次都会执行,浪费性能。等于每执行一个其它操作都要请求一次。
// useEffect 每次都会执行
useEffect(() => {
document.title = `点击了${count}次`;
});
// 因为第一次执行的时候是空数组,第二次也是空数组他们一样,所以这个useEffect只更新一次
useEffect(() => {
ajax().then(data => {
setContent(data);
});
},[]);
手动触发更新:
// 异步请求 ,清除 timeout
useEffect(() => {
console.log('useEffect render')
const getData = async ()=>{
const res = await ajax(query)
console.log(res)
setContent(res)
}
getData()
},[query]); // 传空数组只会执行一次
// 手动改变 query 的值触发 useEffect 的更新调用渲染
<Button
size="small"
type="primary"
onClick={() => setQuery(query+1)}
>
要使用 useEffect 清除定时器,需要使用 useRef,具体请看 15.6 useRef
类似static contextType
的用法
const ThemeContext = React.createContext();
function Context() {
// 类似 contextType
const test = useContext(ThemeContext);
debugger
return <span>{test}</span>; // dark
}
export default function Hooks(){
return (
<ThemeContext.Provider value={'dark'}>
<Context />
</ThemeContext.Provider>
)
}
memo
和 PureComponent
一样是为了判断组件是否重复渲染,达到性能优化的目的。memo
主要用于函数组件 它没有 state
。PureComponent
用于类组件。
useMemo
是用于判断一段函数逻辑是否重复执行。
useMemo
对应函数 fn
memo
对应函数组件
useMemo
是在渲染期间完成,且返回值可用作渲染。
useEffect
是在渲染完成后执行有副作用的操作。
二者写法类似,第二次参数都是数组,用来对比。
如果 useMemo
的返回值是一个函数可以用 useCallback
代替 useMemo
const [testUseMemo,setTestUseMemo] = useState(0)
const testUseCallBack = useMemo(()=>{
return function(){
setTestUseMemo(testUseMemo+1)
}
},[])
// 等价于上面的写法
const testUseCallBack = useCallback(()=>{
setTestUseMemo(testUseMemo+1)
},[])
它们主要应用于给子组件传递参数的时候,是否渲染子组件,达到性能优化的效果
Ref 主要为了获取 dom 以及获取子组件。不支持传入函数参数进行初始化。
在函数组件中我们有三种方式创建 ref
注意:在函数组件上我们不能用上述方法创建 ref
,因为只有类组件才可以实例化。
但是在函数组件中,我们使用 useRef
创建 ref
获取 dom
句柄或子组件
获取组件上次渲染的数据,同步到 ref 中在下次渲染的时候获得。(获取上次的 state)
const refTimer = useRef();
const [count, setCount] = useState(0);
// 用 ref 保存变量
useEffect(() => {
refTimer.current = setInterval(() => {
setCount(count => count + 1);
}, 1000);
}, []);
useEffect(() => {
if (count >= 3) {
clearTimeout(refTimer.current);
}
});
为什么不使用变量保存定时器然后清除?
因为组件每次都会重新渲染,拿到的变量无意义。
没什么好说的把原有的逻辑放到以函数名为use
开头的自定义函数中。
目的就是为了复用代码
// 自定义 Hooks 函数名必须以 use 开头
function useNumber(defaultNumber) {
let [number, setNumber] = useState(defaultNumber);
const ref = useRef()
useEffect(() => {
ref.current = setInterval(() => {
setNumber(number => number + 1);
}, 1000);
}, []);
useEffect(()=>{
if(number>=10){
clearTimeout(ref.current)
}
})
return [number, setNumber];
}
function Counter() {
let [number, setNumber] = useNumber(0);
return (
<Button
type="primary"
onClick={() => {
setNumber(number + 10);
}}
>
点击加10 : {number}
</Button>
);
}
Hooks 函数必须在函数顶层调用,不能在 if...else
等条件语句中使用。
useState
中 state
的唯一性,有序性。只能在函数组件,自定义 Hooks 中使用,为了确保能够在 Hooks 上下文中调用。
use
开头。安装官方推荐的 Hooks 插件eslint-plugin-react-hooks
"eslintConfig": {
"extends": "react-app",
"plugins": [
"react-hooks"
],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
import React,{useState,useEffect,memo, useRef} from 'react'
class ExampleComponent extends React.Component {
// 用于初始化 state
constructor() {}
useState()
// 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
// 因为该函数是静态函数,所以取不到 `this`
// 如果需要对比 `prevProps` 需要单独在 `state` 中维护
static getDerivedStateFromProps(nextProps, prevState) {}
useState()
static getDerivedStateFromError(){}
// Hooks 暂未实现组件错误处理的功能
// 判断是否需要更新组件,多用于组件性能优化
shouldComponentUpdate(nextProps, nextState) {}
memo() // memo 用于函数组件和 Hooks 关系不大
// 组件挂载后调用
// 可以在该函数中进行请求或者订阅
componentDidMount() {}
useEffect(()=>{
componentDidMount() {}
return ()=>{
componentWillUnmount() {}
}
},[])
// 用于获得最新的 DOM 数据
getSnapshotBeforeUpdate() {}
useEffect() 结合 useRef() 用 useRef() 保存上传的状态
// 组件即将销毁
// 可以在此处移除订阅,定时器等等
componentWillUnmount() {}
useEffect()
// 组件销毁后调用
componentDidUnMount() {}
// 组件更新后调用
componentDidUpdate() {}
useEffect()
// 渲染组件函数
render() {}
// 以下函数不建议使用
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate(nextProps, nextState) {}
UNSAFE_componentWillReceiveProps(nextProps) {}
}
文章涉及到的在线事例
用密:admin/admin
参考:
Hooks简介
YCK前端进阶
React Hooks 详解 【近 1W 字】+ 项目实战