Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

精读《React Hooks 最佳实践》 #202

Closed
ascoders opened this issue Aug 29, 2019 · 18 comments
Closed

精读《React Hooks 最佳实践》 #202

ascoders opened this issue Aug 29, 2019 · 18 comments

Comments

@ascoders
Copy link
Owner

本周的精读是笔者总结出的 Hooks 最佳实践经验,欢迎讨论。

@zhixuanziben
Copy link

文章链接在哪?

@ascoders
Copy link
Owner Author

ascoders commented Sep 5, 2019

延到本周了,欢迎讨论。

@nihgwu
Copy link

nihgwu commented Sep 9, 2019

关于 defaultProps, 现在(准确的说是未来)官方的推荐其实就是解构的默认参数来取代 defaultProps,未来的React会去掉对defaultProps的支持以简化内部流程,至于里面提到默认的object参数会每次会重新创建的问题,目前的推荐方案在外面创建默认值 https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#deprecate-defaultprops-on-function-components

还有,这个最佳实践有太多的过度优化的地方,比如 useCallback 以及 useMemo,尤其是 useMemo来模拟React.memo,这种应该算是anti-pattern吧,useMemo不是为了这种场景设计的,未来React有可能直接将memo內建,那现在这种方式就会变得毫无意义

@LiHaoGit
Copy link

LiHaoGit commented Sep 9, 2019

关于 defaultProps, 现在(准确的说是未来)官方的推荐其实就是解构的默认参数来取代 defaultProps,未来的React会去掉对defaultProps的支持以简化内部流程,至于里面提到默认的object参数会每次会重新创建的问题,目前的推荐方案在外面创建默认值 https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#deprecate-defaultprops-on-function-components

还有,这个最佳实践有太多的过度优化的地方,比如 useCallback 以及 useMemo,尤其是 useMemo来模拟React.memo,这种应该算是anti-pattern吧,useMemo不是为了这种场景设计的,未来React有可能直接将memo內建,那现在这种方式就会变得毫无意义

+1
相关文章重新思考 Hooks memoization

@crimx
Copy link

crimx commented Sep 9, 2019

useEffect 那里无需这么麻烦,应该把 props 函数的依赖去掉,useEffect 执行回调时是调用最新的回调,所以对 props 的引用也必然是最新的,所以父组件传的函数怎么变都无所谓了。

useEffect(() => {
  props.onChange(props.id)
}, [props.id])

@Tan90Qian
Copy link

useEffect 那里无需这么麻烦,应该把 props 函数的依赖去掉,useEffect 执行回调时是调用最新的回调,所以对 props 的引用也必然是最新的,所以父组件传的函数怎么变都无所谓了。

useEffect(() => {
  props.onChange(props.id)
}, [props.id])

这么做会导致eslint-plugin-react-hooks插件的rules报错,因为在useEffect内部调用了props.onChange,但依赖列表中却没有出现。这个插件对于不太熟悉的hooks的开发人员是一个很好的保护措施,不太建议关闭。在这个前提下,useEffect中使用到的任何变量和函数都应出现在依赖列表中。(当然如果你们团队能保证项目的所有维护人员都不会引起死循环之类的问题,那确实可以这样关了规则之后这样写)

@crimx
Copy link

crimx commented Sep 9, 2019

这个插件对于不太熟悉的hooks的开发人员是一个很好的保护措施,不太建议关闭。

然而加上了也会出现死循环,这貌似并没有起到保护新人的作用。而反设计要求父组件来妥协,这是在给自己和使用者挖坑。

所以 exhaustive-deps 检查其实是不完美的,因为怎么用存在二义性,如果不理解 hooks 检不检查都会踩坑。更好的方式是把复杂的 hooks 拆分通过组合方式构建,每个 hooks 目的单一明确。

如果非要用 exhaustive-deps 检查,一个折中的方式是用 ref

const onChangeRef = useRef(props.onChange)
onChangeRef.current = props.onChange

useEffect(() => {
  onChangeRef.current(props.id)
}, [props.id])

@Tan90Qian
Copy link

所以 exhaustive-deps 检查其实是不完美的,因为怎么用存在二义性,如果不理解 hooks 检不检查都会踩坑。更好的方式是把复杂的 hooks 拆分通过组合方式构建,每个 hooks 目的单一明确。

如果非要用 exhaustive-deps 检查,一个折中的方式是用 ref

const onChangeRef = useRef(props.onChange)
onChangeRef.current = props.onChange

useEffect(() => {
  onChangeRef.current(props.id)
}, [props.id])

其实这也不能完全算是一个缺陷吧,毕竟useEffect在很大程度上可以当做类组件中componentDidMountcomponentDidUpdatecomponentWillUnmount的综合体。

在函数式组件中使用useEffect来实现一些响应式功能的逻辑,基本可以在类组件中转化为分散在不同生命周期中的逻辑(当然对于框架底层逻辑来说确实是有明显差异的)。

而在生命周期中如果你想要实现同样的功能,其实一样会遇到这些问题。这是引用类型天然的问题,而不是lint rule的缺陷,只是因为hooks提供了一个快车道导致这类问题暴露的更频繁而已。

@crimx
Copy link

crimx commented Sep 9, 2019

我是指 exhaustive-deps 检查是不完美的,而没有否定 useEffect 的作用。

@Tan90Qian
Copy link

我觉得对于使用了hooks的函数式组件来说,真正存在本质区别的,其实是因为this的消失而带来的渲染时对propsstate的捕获差异。其他的问题函数式组件中存在,类组件中也一样存在,无非是水平高的能提前避免,水平次些的踩几次坑后记住而已。

@crimx
Copy link

crimx commented Sep 9, 2019

这又是另外一个话题了。目前(从您第一个回复开始)讨论的是,权衡需不需要引用不完美的检查规则,并为此编写额外代码。这不是 useEffect 的问题,而是 lint 规则的问题。

@Tan90Qian
Copy link

我是指 exhaustive-deps 检查是不完美的,而没有否定 useEffect 的作用。

但问题是,你不能保证props的提供者每次都能提供“同样功能”的函数,毕竟使用了hooks的组件无法确认它会在什么样的场景下被使用。可能父组件组件在给子组件传递函数时采用了一个三目运算符来使得子组件在父组件持有不同状态时执行不同的功能。

@crimx
Copy link

crimx commented Sep 9, 2019

但问题是,你不能保证props的提供者每次都能提供“同样功能”的函数,毕竟使用了hooks的组件无法确认它会在什么样的场景下被使用。可能父组件组件在给子组件传递函数时采用了一个三目运算符来使得子组件在父组件持有不同状态时执行不同的功能。

这并不影响我以上提供的两种解决方式。

@Tan90Qian
Copy link

这并不影响我以上提供的两种解决方式。

我不否认这一点,但这条lint就我个人来看也确实很难做的更完美了,毕竟这是运行时的问题了。我还是觉得,开启lint规则至少可以降低1/3甚至一半的常见问题(而且也可以算是一种好习惯),然后可以采取您上面提出的这种通过useRef的方式来避免剩下一半的常见问题。

@yzw7489757
Copy link

yzw7489757 commented Oct 22, 2019

用代码假设业务,有一组图片信息需要编辑,怎么才能更简单的防止触发其他组件的re-render,按道理bind会返回一个新的函数,类似双箭头函数,会每次进行re-render。

var zone = {
	title: 'space1',
	dataList: [
		{ img:'xxx1.png', title: 'p1' },
		{ img:'xxx2.png', title: 'p2' },
	]
}
// 合并配置
const mergeMultipleOption = useCallback((attr, val) => {
    setMultipleOption(pre => ({
      ...pre,
      [attr]: value,
    }));
  }, []);

// attr 作为预留 mergeMultipleOption 第一个参数
const triggerToMergeMultipleOption = useCallback(
    (attr) => mergeMultipleOption.bind(null, attr),
 	[]);

// 更新list
const updateImg = useCallback((index, value) => {
    const newImgList = dataList.map((data, idx) => {
      if (idx === index) {
        return {
          ...data,
          img: value,
        };
      }
      return data;
    });
    triggerToMergeMultipleOption('dataList')(newImgList);
  }, [dataList]);

// index 预留下标
const triggerToUpdateImg = useCallback(
    (index) => updateImg.bind(null, index), 
	[zone.dataList],);

return (
	zone.dataList.map((item,idx=>(
		<Upload type="file" onChange={triggerToUpdateImg(index)} />
	))
)

@kltk
Copy link

kltk commented Oct 25, 2019

用代码假设业务,有一组图片信息需要编辑,怎么才能更简单的防止触发其他组件的re-render,按道理bind会返回一个新的函数,类似双箭头函数,会每次进行re-render。

可以把 index 往下传,onChange(value) => onChange(value, name) ,这样就不用单独 bind 了
还可以通过 ref 解除对数据的信赖,这样 handleUpload 的引用就一直保持不变,这个前面也有人提过

const [zone, setZone] = useState({
  title: 'title',
  dataList: [],
});

const ref = useRef();
// 每次渲染实时更新数据
ref.current = zone;

// 回调 index, 不用对每个元素 bind
const handleUpload = useCallback((img, index) => {
  // 通过 ref 引用实时数据
  const { dataList } = ref.current;
  const newData = { ...dataList[index], img };
  const newDataList = Object.assign([], dataList, { [index]: newData });
  const newZone = {...ref.current, dataList: newDataList};
  setZone(newZone);
}, []);

return zone.dataList.map((item, index) => (
  // Upload 需要支持 name,或者封装一下
  <Upload name={index} value={item} onChange={handleUpload} />
));

@linfanxxxx
Copy link

@ascoders 请问文章中的useAsync这个hooks是哪里的,在react文档中没有找到

@ascoders
Copy link
Owner Author

@linfanxxxx react-use

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants