直接回答:useEffect 里用到的函数尽量写在 effect 内部,依赖数组就不会因为函数每次重建而失效;状态更新不生效,多半是闭包拿到了旧值,用函数式更新 setState(prev => ...) 能解决一大半。

effect中需要的函数尽量在effect中声明

因为effect其实是创建了一个闭包,如果effect中使用了组件作用域中的值,那么那个值变化如果想要能及时体现出来需要在effect的第二个参数中传入依赖的那个值,如果effect中使用了外部定义的函数则很难发现到底使用了哪个组件作用域中的值,如果忘记在第二个参数中传入就会很麻烦

1
2
3
4
5
6
7
8
9
function Example({ someProp }) {
function doSomething() {
console.log(someProp);
}

useEffect(() => {
doSomething();
}, []); // � 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)
}

要记住 effect 外部的函数使用了哪些 props 和 state 很难。这也是为什么 通常你会想要在 effect 内部 去声明它所需要的函数。 这样就能容易的看出那个 effect 依赖了组件作用域中的哪些值:

1
2
3
4
5
6
7
8
9
function Example({ someProp }) {
useEffect(() => {
function doSomething() {
console.log(someProp);
}

doSomething();
}, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)
}

如果这样之后我们依然没用到组件作用域中的任何值,就可以安全地把它指定为 []:

1
2
3
4
5
6
7
useEffect(() => {
function doSomething() {
console.log('hello');
}

doSomething();
}, []); // ✅ 在这个例子中是安全的,因为我们没有用到组件作用域中的 *任何* 值

惰性初始state的方法

1
2
3
4
5
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});

这个函数只会执行一次,适合于如果初始值需要复杂计算才能得到的情况,如果是提前计算好那么每次在刷新的时候都会重新计算一次,浪费了计算资源

1
2
const initialState = someExpensiveComputation(props);
const [state, setState] = useState(initialState);

想这样的每次状态更新都会重新计算initalState的值,但是又用不上

useEffect 是在渲染后运行的

所以ref在effect中的dom其实是一直都有的,effect是在渲染后,下次渲染前调用

React.memo优化组件性能

React.memo类似于PureCompoent 作用是优化组件性能,防止组件触发重渲染

React.memo针对 一个组件的渲染是否重复执行

1
2
3
4
const Foo=({name,age})=>{}
React.mome(<Foo />); // 可以传第二个参数,如果不传默认就是Foo组件中的frops,
React.mome(<Foo />,[name]);// 这样就只有name改变的时候Foo组件才重新渲染,否则还用上一次渲染结果,也就是Foo函数根本不调用

1
()=>{}

useEffect是在渲染之后完成的

useMemo是在渲染期间完成的

useCallback

1
useMemo( ()=>{fn} ) 等价于 useCallback(fn)

React.memo

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。 默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

1
2
3
4
5
6
7
8
9
10
11
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);

useMemo 返回的是缓存的函数的值 ,针对一段函数逻辑是否重复执行

比较适用于局部渲染的优化,只有第二个参数里的值改变了,函数才重新计算,否则不重新计算

useCallback返回的是缓存的函数

感觉useCallback还是会在每次刷新的时候计算下只不过返回的函数的地址是一样的

1
useMemo(()=>{},[])

参数如果是空数组的话就只会执行一次

什么时候使用useCallback

  1. 函数 functionA 在 useEffect 内部被调用,为了避免 useEffect 的频繁触发,所以我用useCallback将函数 functionA 包起来;

  2. 函数 functionB 在另一个使用useCallback包裹的函数 functionC 中被调用,为了避免这个函数functionC频繁变更,所以我用useCallback将函数 functionB 包起来;

  3. 当函数 functionD 被传递给子组件,为了避免子组件进行不必要的频繁渲染,所以我用useCallback将函数functionD 包起来[※]

场景一

在某些场景里需要在useEffect里调用一个函数,出于某些原因.你无法把这个函数移动到effect内部;

1
2
3
4
5
6
7
8
9
10
11
const Example = ({ someProp }) => {

const doSomething = () => {
console.log(someProp);
}

useEffect(() => {
doSomething();
}, [doSomething]); // � 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)
}

因为useEffect中调用的 doSomething 函数使用了 someProp这个props,所以需要将doSomething放在useEffect的依赖列表里;

而为了确保这个函数不会随着组件渲染而重新创建,我们需要用useCallback把doSomething包裹起来,如下:

1
2
3
4
5
6
7
8
9
10
11
const Example = ({ someProp }) => {
// ✅ 用 useCallback 包裹以避免随渲染发生改变
const doSomething = useCallback(() => {
// ... Does something with someProp ...
}, [someProp]); // ✅ useCallback 的所有依赖都被指定了

useEffect(() => {
doSomething();
}, [doSomething]); // ✅ useEffect 的所有依赖都被指定了
}

场景二

类似于场景一, 在某些情况下,你需要在useCallback包裹的函数中调用另一个函数,记得要给另一个函数也包上useCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Example = ({ prop1, prop2 }) => {
// ✅ 用 useCallback 包裹以避免随渲染发生改变
const something = useCallback(() => {
// ... Does something with prop2 ...
}, [prop2]); // ✅ useCallback 的所有依赖都被指定了

// ✅ 用 useCallback 包裹以避免随渲染发生改变
const doSomething = useCallback(() => {
somethingOne();
// ... Does something with prop1 ...
}, [prop1, somethingOne]); // ✅ useCallback 的所有依赖都被指定了

useEffect(() => {
doSomething();
}, [doSomething]); // ✅ useEffect 的所有依赖都被指定了
}

typescript中如果自己需要在window下挂属性

在index.tsx文件中加入下面代码,在使用就不会报错

1
2
3
4
5
6
7
declare global {
interface Window {
electron: { ipcRenderer: IpcRenderer };
jayson: any;
data: any;
}
}

生产环境和测试环境使用的远程ip不一样

使用的umi-request 中间间中加入下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const request = extend({
errorHandler,
// 默认错误处理
credentials: 'include', // 默认请求是否带上cookie
});
console.log(process.env.NODE_ENV);
if (process.env.NODE_ENV !== 'development') {
request.use(async (ctx, next) => {
const { req } = ctx;
const { url, options } = req;
const ip = window.data.ip;
const port = window.data.port;
ctx.req.url = 'http://' + ip + ':' + port + url;
await next();
});
}

这篇笔记整理自我自己的实践记录,如果做法有出入,或者你踩过别的坑,欢迎到留言板一起聊聊。

站内搜索

没有找到内容!