-
Notifications
You must be signed in to change notification settings - Fork 0
React Tips
-
Q:为什么要有key?
-
A:diff的时候可以直接对比key,复用一些未更新的node,提高效率。。。
-
我遇到的场景:页面左侧是菜单,右侧是菜单对应的table,当菜单menuItem变了那么对应的tableContent也要展示出来,而tableContent变了时要保存,如果未保存就去切换菜单,那么需要给出提示,点击保存/取消之后才能切换。
-
问题来了:右侧table单独抽成一个组件(因为别的地方也要用),然后抛出contentChanged,content等字段给左侧/全局,用于切换角色时判断table内容有无变化,而table的初始值是不一定相同的,那么我需要这个table组件自己去维护自己的状态,比如初始化的时候contentChanged=false,content=接口返回的数据/[],而这个table组件又需要接收menuItem来获取对应的数据,这时,如果不给table组件加一个key={menuItem}的话,那么我切换左侧menuItem之后,组件抛出来的content就不会被重新初始化,那么我此时去保存这个tableContent就会出现bug——明明table里是空的,而保存之后table里反而有了数据,这数据还是上一个menuItem对应的数据。而解决这个bug只需要给table组件加个key,让它跟menuItem绑定,menuItem改变时table组件会销毁重新创建,那么content自然也会跟着初始化。
- example:
// 1st.
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return <h1>Now: {count}, before: {prevCount}</h1>;
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}// 2nd.
const prevSearchText = useRef();
useEffect(() => {
return (function (searchText) {
return function () {
prevSearchText.current = searchText;
};
})(props.searchText);
}, [props.searchText]);- 虽说已经不太提倡用这玩意了,but还是挺有用的:组件的生命周期,指的是一个 React 组件从挂载,更新,销毁过程中会执行的生命钩子函数。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentWillMount() {}
componentDidMount() {}
componentWillUpdate(nextProps, nextState) {}
componentWillReceiveProps(nextProps) {}
componentDidUpdate(prevProps, prevState) {}
shouldComponentUpdate(nextProps, nextState) {}
componentWillUnmount() {}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}-
constructor,顾名思义,组件的构造函数。一般会在这里进行 state 的初始化,事件的绑定等等
-
componentWillMount,是当组件在进行挂载操作前,执行的函数,一般紧跟着 constructor 函数后执行
-
componentDidMount,是当组件挂载在 dom 节点后执行。一般会在这里执行一些异步数据的拉取等动作
-
shouldComponentUpdate,返回 false 时,组件将不会进行更新,可用于渲染优化
-
componentWillReceiveProps,当组件收到新的 props 时会执行的函数,传入的参数就是 nextProps ,你可以在这里根据新的 props 来执行一些相关的操作,例如某些功能初始化等
-
componentWillUpdate,当组件在进行更新之前,会执行的函数
-
componentDidUpdate,当组件完成更新时,会执行的函数,传入两个参数是 prevProps 、prevState
-
componentWillUnmount,当组件准备销毁时执行。在这里一般可以执行一些回收的工作,例如 clearInterval(this.timer) 这种对定时器的回收操作
-
官方16.3/16.4版本生命周期图
-
简单易懂版
-
当首次挂载组件时,按顺序执行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount。
-
当卸载组件时,执行 componentWillUnmount。
-
当重新挂载组件时,此时按顺序执行 getInitialState、componentWillMount、render 和 componentDidMount,但并不执行 getDefaultProps。
-
当再次渲染组件时,组件接受到更新状态,此时按顺序执行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
-
当使用 ES6 classes 构建 React 组件时,static defaultProps = {} 其实就是调用内部的 getDefaultProps方法,constructor 中的 this.state = {} 其实就是调用内部的 getInitialState 方法。
-
补充&更新
- getDerivedStateFromProps(nextProps, prevState) 代替componentWillReceiveProps()。 老版本中的componentWillReceiveProps()方法判断前后两个 props 是否相同,如果不同再将新的 props 更新到相应的 state 上去。这样做一来会破坏 state 数据的单一数据源,导致组件状态变得不可预测,另一方面也会增加组件的重绘次数。
这两者最大的不同就是: 在 componentWillReceiveProps 中,我们一般会做以下两件事,一是根据 props 来更新 state,二是触发一些回调,如动画或页面跳转等。
在老版本的 React 中,这两件事我们都需要在 componentWillReceiveProps 中去做。 而在新版本中,官方将更新 state 与触发回调重新分配到了 getDerivedStateFromProps 与 componentDidUpdate 中,使得组件整体的更新逻辑更为清晰。而且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去做其他一些让组件自身状态变得更加不可预测的事情。
- getSnapshotBeforeUpdate(prevProps, prevState) 代替componentWillUpdate。 常见的 componentWillUpdate 的用例是在组件更新前,读取当前某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的处理。 这两者的区别在于:
在 React 开启异步渲染模式后,在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在 componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。 getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。 此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
- 阶段一:MOUNTING
- mountComponent 负责管理生命周期中的 getInitialState、componentWillMount、render 和 componentDidMount。
- 由于 getDefaultProps 是通过构造函数进行管理的,所以也是整个生命周期中最先开始执行的。而 mountComponent 只能望洋兴叹,无法调用到 getDefaultProps。这就解释了为何 getDefault-Props只执行一次。
- 在 componentWillMount 中调用 setState 方法,是不会触发 re-render的,而是会进行 state 合并,因此 componentWillMount 中的 this.state 并不是最新的,在 render 中才可以获取更新后的 this.state。
- 阶段二:RECEIVE_PROPS
- updateComponent 负责管理生命周期中的 componentWillReceiveProps、shouldComponent-Update、componentWillUpdate、render 和 componentDidUpdate。
- 在 componentWillReceiveProps 中调用 setState,是不会触发 re-render 的,而是会进行 state 合并。且在 componentWillReceiveProps、shouldComponentUpdate 和 componentWillUpdate 中也还是无法获取到更新后的 this.state,即此时访问的 this.state 仍然是未更新的数据,因此只有在 render 和 componentDidUpdate 中才能获取到更新后的 this.state。
- 调用 shouldComponentUpdate 判断是否需要进行组件更新,如果存在 componentWillUpdate,则执行。
- 禁止在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,这会造成循环调用,直至耗光浏览器内存后崩溃。
- 阶段三:UNMOUNTING
- unmountComponent 负责管理生命周期中的 componentWillUnmount。
- 如果存在 componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态,如果此时在 componentWillUnmount 中调用 setState,是不会触发 re-render 的,这是因为所有更新队列和更新状态都被重置为 null,并清除了公共类,完成了组件卸载操作。
-
路由包括:react-router-dom
-
react-router-dom导出的组件:
-
HashRouter 和 BrowserRouter 根容器
-
Switch 精准匹配
-
Route 路由规则
-
Link 声明式跳转
-
NavLink 声明式跳转 + 样式
-
Redirect 重定向
-
withRouter 高阶组件,将组件变成路由组件
- basic usage
import React, { useState } from "react";
import constate from "constate";
// 1️⃣ Create a custom hook as usual
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(prevCount => prevCount + 1);
return { count, increment };
}
// 2️⃣ Wrap your hook with the constate factory
const [CounterProvider, useCounterContext] = constate(useCounter);
function Button() {
// 3️⃣ Use context instead of custom hook
const { increment } = useCounterContext();
return <button onClick={increment}>+</button>;
}
function Count() {
// 4️⃣ Use context in other components
const { count } = useCounterContext();
return <span>{count}</span>;
}
function App() {
// 5️⃣ Wrap your components with Provider
return (
<CounterProvider>
<Count />
<Button />
</CounterProvider>
);
}const Parent = () => {
const [items, setItems] = useState([
{ value: '' },
{ value: '' },
{ value: '' }
])
return (
<div>
{items.map((item, index) => (
<Item
key={index}
id={index}
value={item.value}
onChange={(id, value) =>
setState(state.map((item, index) => {
return index !== id ? item : { value: value }
})}
/>
)}
</div>
)
}- Anonymous functions will always get a new reference on every render. 匿名函数总是会在每次渲染时获得新的引用.
- This means that onChange property will change every time Parent renders. To prevent that, we need to memoize it with useCallback. 这意味着每次 Parent 渲染时 onChange 属性都会改变。为了防止这种情况,我们需要使用 useCallback 记住它。
- React does not care whether “props changed” - it will render child components unconditionally just because the parent rendered!React 并不关心“props 是否改变”——它会无条件地渲染子组件,因为父组件渲染了!
- If you don’t want a component to re-render when its parent renders, wrap it with memo. After that, the component indeed will only re-render when its props change.如果您不希望组件在其父级渲染时重新渲染,请使用 memo 包装它。之后,该组件确实只会在其 props 更改时重新渲染。
- 有时使用useCallback来对函数做一个缓存,但是由于对state的依赖,导致这个缓存并不生效/效果不及预期,那么需要停止对这个state的依赖,此时可以使用functional state update--函数状态更新?例如:
// 失效:items更新会导致所有的list item都重新渲染
const Parent = () => {
...
const onChange = useCallback((id, value) => {
setItems(items.map((item, index) => {
return index !== id ? item : { value: value }
}))
}, [items])
return (
<div>
{items.map((item, index) => (
<Item
key={index}
id={index}
value={item.value}
onChange={onChange}
/>
)}
</div>)
}
// 有效
const onChange = useCallback((id, value) => {
setItems(prevItems => prevItems.map((item, index) => {
return index !== id ? item : { value: value }
}))
}, []) // No dependencies-
- setState 只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。
-
- setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
-
- setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
-
- useEffect hooks中,useState都是异步的。
-
所以严格说是同步的代码, 毕竟都在一个eventloop里, 只不过setstate里的参数/回调被延迟执行到下面代码执行完才执行。
- Updating the state inside the render. 比如:在class的render()函数 或 函数组件的return之外。Fix: 使用useEffect包裹。
- Infinite loop in useEffect. 比如:更新的state被放到依赖数组里。Fix: 使用setSate(prev=>prev+1),即使用一个函数接收prevState,然后进行更新。
- Incorrectly set event handlers. 比如:新手常犯错误:onClick事件应该接收一个闭包函数,而不是直接传入函数执行后的结果。
-
react中的事件是合成事件,会统一绑定在最外层;为什么是合成事件?--因为用了虚拟DOM。
-
Virtual DOM 在内存中是以对象的形式存在的,如果想要在这些对象上添加事件,就会非常简单。
-
React 基于 Virtual DOM 实现了一个 SyntheticEvent (合成事件)层,我们所定义的事件处理器会接收到一个 SyntheticEvent 对象的实例,它完全符合 W3C 标准,不会存在任何 IE 标准的兼容性问题。并且与原生的浏览器事件一样拥有同样的接口,同样支持事件的冒泡机制,我们可以使用 stopPropagation() 和 preventDefault() 来中断它。所有事件都自动绑定到最外层上。如果需要访问原生事件对象,可以使用 nativeEvent 属性。
-
例如:对于a标签要取消他的默认跳转行为时,在原生html中:return false;在React中要:e.preventDefault();
import React, { useEffect, useState } from 'react';
const useWindowWidth = () => {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return width;
};
export default useWindowWidth;