@程序员,React 使用如何避坑?

百家 作者:CSDN 2019-10-02 01:48:40
@程序员,如何更好地写React?
作者 | Alex K
译者 | 苏本如,责编 | 郭芮
出品 | CSDN(ID:CSDNnews)
以下为译文:

在Stack Overflow上回答与React框架相关的问题时,我注意到人们对于这个框架有几类主要的问题。我决定将一些最常见的问题和如何处理这些问题的解决方法写出来,以期对那些还不熟悉React框架的人,或那些正在努力掌握其基本概念的人有所帮助。在本文中,对于使用基于类组件和使用钩子(hook)的函数组件遇到的问题,都会交叉谈到。


直接修改状态


React中的状态被认为是不可变的,因此不应该直接修改。如果要修改状态值,应该使用一个特殊的setState方法和useState钩子中的setter函数。考虑下面的例子,在这个例子中,你希望根据复选框(checkbox)的状态更新数组中特定对象的checked字段。

    const updateFeaturesList = (e, idx) => {
      listFeatures[idx].checked = e.target.checked;
      setListFeatures(listFeatures);
    };

这段代码的问题在于,对状态的更改不会反映到UI中,因为状态更新使用了相同的对象引用,因此不会触发重新渲染(re-render)动作。不能直接改变状态的另一个重要原因是,由于它的异步特性,后面的状态更新可能会覆盖直接对状态所做的更新,从而导致一些无法查清的错误。在这种情况下,正确的方法是使用useState的setter方法。

 const updateFeaturesList = (e, idx) => {
      const { checked } = e.target;
      setListFeatures(features => {
        return features.map((feature, index) => {
          if (id === index) {
            feature = { ...feature, checked };
          }
          return feature;
        });
      });
    };

通过使用map和object spread(对象展开),我们还能确保不会更改原始状态项。


在初始状态上设置错误的值类型


将初始状态值设置为null或空字符串,然后在render方法中访问该值的属性,就好像访问一个对象一样,这是一种很常见的错误。同样的常见错误还有,不为嵌套对象提供默认值,然后尝试在render方法或其他组件方法中访问它们。

class UserProfile extends Component {
      constructor(props) {
        super(props);

        this.state = {
          usernull
        };
      }

      componentDidMount() {
        fetch("/api/profile").then(data => {
          this.setState({ user: data });
        });
      }

      render() {
        return (
          < div>
            < p>User name:p>
            < p>{this.state.user.name}p> // Cannnot read property 'name' of null
          div>
        );
      }
    }
如果将初始状态的值设置为空数组,然后尝试访问这个空数组中的第n个项,也会发生类似的错误。当通过API调用来获取数据时,组件将以提供的初始状态渲染,并且尝试访问null或未定义元素上的属性,这也将导致错误。因此,让初始状态立即被更新,这一点很重要。
在我们的例子里,正确的状态初始化应该像下面这样:
 class UserProfile extends Component {
      constructor(props) {
        super(props);

        this.state = {
          user: {
            name""
            // Define other fields as well
          }
        };
      }

      componentDidMount() {
        fetch("/api/profile").then(data => {
          this.setState({ user: data });
        });
      }

      render() {
        return (
          < div>
            < p>User name:p>
            < p>{this.state.user.name}p> // Renders without errors
          div>
        );
      }
    }

从用户体验的角度来看,最好展示某种loader的结果给用户,直到数据被正确地获取到。


忘记setState是异步的


另一个常见的错误是试图在设置状态值之后立即访问它。

   handleChange = count => {
      this.setState({ count });
      this.props.callback(this.state.count); // Old state value
    };

设置新值不会立即生效,通常它会在下一个可用的渲染上完成,或者可以进行批量处理以优化性能。因此,在设置状态值之后立即访问该值可能不会得到最新的更新结果。这个问题可以通过使用setState的可选的第二个参数来解决,这个参数是一个回调函数,它在状态值被最新的值更新完成后会被调用。

  handleChange = count => {
      this.setState({ count }, () => {
        this.props.callback(this.state.count); // Updated state value
      });
    };

不过,这与钩子(hook)的做法有很大不同,因为useState钩子的setter函数没有第二个类似于setState的回调参数。在这种情况下,官方推荐的做法是使用useEffect钩子。

    const [count, setCount] = useState(0)

    useEffect(() => {
      callback(count); // Will be called when the value of count changes
    }, [count, callback]);

    const handleChange = value => {
      setCount(value)
    };

应该注意的是,setState方法严格来说并不是异步的,只不过它返回的是一个预期(promise)。因此,对它进行async/await操作或使用then将不起作用(这是另一个常见的错误)。


错误地依赖当前状态值来计算下一个状态


这个问题与上面讨论的问题有关,因为它还是和异步状态更新相关。见下例:

   handleChange = count => {
      this.setState({ countthis.state.count + 1 }); // Relying on current value of the state to update it
    };

上面代码中的这种更新方式存在的问题是:在设置新状态时,count的值可能没有正确更新,这将导致新状态值的设置不正确。正确的方法是使用setState的函数形式。

   increment = () => {
      this.setState(state => ({ count: state.count + 1 })); // The latest state value is used
    };

setState的函数形式在更新被执行时有第二个参数 - props,可以以和state参数类似的方式使用。

同样的逻辑也适用于useState钩子,其中setter接受函数作为参数。

   const increment = () => {
     setCount(currentCount => currentCount + 1)
    };


忽略useEffect的dependency数组


这是一个不太常见的错误,但仍然时有发生。即使有完全有效的情况可以忽略useEffect的dependency数组,但在其回调函数更新状态时这样做可能会导致无限循环。



将非基元类型的对象或其它值传递给useEffect的dependency数组


与上面的情况类似,但更微妙的错误是跟踪对象、数组或effect钩子的dependency数组中的其他非基元值。考虑下面的代码:

   const features = ["feature1""feature2"];
    useEffect(() => {
      // Callback 
    }, [features]);

在这里,当我们将数组作为一个dependency数组传递时,React将只存储对它的引用,并将其与数组的上一个引用进行比较。但是,由于它是在组件内部声明的,因此在每次渲染时都会重新创建features数组,这意味着它的引用每次都是新的,因此不等于useEffect跟踪的引用。最终,即使数组没有被更改,回调函数也会在每个render方法上运行。对于基元类型的值(如字符串和数字)来说,这不是问题,因为它们在JavaScript中是按值来比较的,而不是按引用来比较。

有几种方法可以解决这个问题。第一个方法是将变量声明移到组件之外,这样就不会在每次渲染时重新创建它。但是,在某些情况下,这是不可能的,例如,如果我们正在跟踪的props,或者跟踪的依赖项是组件状态的一部分。另一种方法是使用自定义的deep compare hook来正确地跟踪依赖项引用。而更简单的解决方法是将值包装到usememohook中,这种做法会在重新渲染期间保留引用。

    const features = useMemo(() => ["feature1""feature2"], []);

    useEffect(() => {
      // Callback 
    }, [features]);

希望上面的这个列表能够帮助你避免最常见的React使用问题,并提高对主要问题的理解。

如果你有关于这篇文章的任何问题/评论或其他类型的反馈,请在此评论或在推特上告诉我。

原文:https://dev.to/clarity89/the-most-common-mistakes-when-using-react-45h2

本文为 CSDN 翻译,转载请注明来源出处。

【END】

CSDN 博客诚邀入驻啦!

本着共享、协作、开源、技术之路我们共同进步的准则,

只要你技术够干货,内容够扎实,分享够积极,

欢迎加入 CSDN 大家庭!

扫描下方二维码,即刻加入吧!

 热 文 推 荐 

☞谷歌、IBM 们的“量子争霸”迷局
Python 分析热门旅游景点,告诉你哪些地方好玩、便宜、人又少!
绝对不止你一人觉得软件贵得离谱!
云栖大会|当数据中台遇上智能 看中台“鼻祖”阿里巴巴又有什么新花样?
年薪 170 万阿里 P8 程序员征婚上热搜,程序员婚恋观大曝光!
对比C++和Python,谈谈指针与引用
肖仰华:知识图谱构建的三要素、三原则和九大策略 | AI ProCon 2019
以太坊交易量第一合约FAIRWIN被爆漏洞, 竟是因为这个接口被滥用……
点击阅读原文,即刻阅读《程序员大本营》最新期刊。
你点的每个“在看”,我都认真当成了喜欢

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接