React中的反模式或对初学者的提示

哈Ha

自从我开始学习React以来已经整整一年了。 在这段时间里,我设法发布了几个用React Native编写的小型移动应用程序,并参与了使用ReactJS开发Web应用程序的工作。 总结并回顾一下我设法踩过的所有耙子,我想到了以文章的形式表达我的经验的想法。 我会保留一点,在开始研究反应之前,我在c ++,python方面已有3年的开发经验,并且认为前端开发没有什么复杂的知识,并且不难理解所有内容。 因此,在最初的几个月中,我忽略了阅读教育文献,而基本上只是谷歌现成的代码示例。 因此,一个首先研究文档的典范开发人员很可能不会在这里为自己找到任何新东西,但是我仍然认为,研究新技术时,相当多的人更喜欢从实践到理论的方法。 因此,如果这篇文章使某人摆脱了困境,那么我尝试没有白费。

技巧1.使用表格


经典情况:存在一个带有多个字段的表单,用户可以在其中输入数据,然后单击按钮,然后将输入的数据发送到外部api /状态保存/显示在屏幕上-在必要的位置加上下划线。

选项1。如何不做


在React中,您可以创建指向DOM节点或React组件的链接。

this.myRef = React.createRef(); 

使用ref属性,可以将创建的链接附加到所需的组件/节点。

 <input id="data" type="text" ref={this.myRef} /> 

因此,可以通过为表单的每个字段创建引用来解决上述问题,并且在单击按钮时调用的函数主体中,通过联系必要的链接来从表单获取数据。

 class BadForm extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); this.onClickHandler = this.onClickHandler.bind(this); } onClickHandler() { const data = this.myRef.current.value; alert(data); } render() { return ( <> <form> <label htmlFor="data">Bad form:</label> <input id="data" type="text" ref={this.myRef} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } } 

内在的猴子如何尝试证明这一决定的合理性:

  1. 最有效的方法是,您仍然有100500个任务,并且没有观看电视节目;没有关闭门票。 那样留着,然后改变
  2. 查看处理表单所需的代码量。 声明为ref并在任何位置访问数据。
  3. 如果将值存储为状态,则每次更改输入数据时,整个应用程序将再次呈现,并且只需要最终数据。 因此,这种方法对优化也很有用,只要这样就可以了。

为什么猴子错了:

上面的示例是React中的经典反模式,它违反了单向数据流的概念。 在这种情况下,您的应用程序将无法响应输入期间的数据更改,因为它们未存储在状态中。

选项2.经典解决方案


对于每个表单字段,都会在将要存储输入结果的状态下创建一个变量。 为value属性分配了此变量。 为onhange属性分配了一个函数,其中状态变量的值通过setState()进行更改。 因此,所有数据均取自状态,并且当数据更改时,状态更改,并且再次呈现应用程序。

 class GoodForm extends React.Component { constructor(props) { super(props); this.state = { data: '' }; this.onChangeData = this.onChangeData.bind(this); this.onClickHandler = this.onClickHandler.bind(this); } onChangeData(event) { this.setState({ data: event.target.value }); } onClickHandler(event) { const { data } = this.state; alert(data); } render() { const { data } = this.state; return ( <> <form> <label htmlFor="data">Good form:</label> <input id="data" type="text" value={data} onChange={this.onChangeData} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } } 

选项3.高级。 当形式变得很多


第二个选项有很多缺点:大量的标准代码,对于每个字段,必须声明onhange方法并向state添加变量。 在验证输入的数据并显示错误消息时,代码量甚至更多。 为了简化表单的工作,有一个出色的Formik库,该库可以处理与表单维护相关的问题,并且还可以轻松添加验证方案。

 import React from 'react'; import { Formik } from 'formik'; import * as Yup from 'yup'; const SigninSchema = Yup.object().shape({ data: Yup.string() .min(2, 'Too Short!') .max(50, 'Too Long!') .required('Data required'), }); export default () => ( <div> <Formik initialValues={{ data: '' }} validationSchema={SigninSchema} onSubmit={(values) => { alert(values.data); }} render={(props) => ( <form onSubmit={props.handleSubmit}> <label>Formik form:</label> <input type="text" onChange={props.handleChange} onBlur={props.handleBlur} value={props.values.data} name="data" /> {props.errors.data && props.touched.data ? ( <div>{props.errors.data}</div> ) : null} <button type="submit">Ok</button> </form> )} /> </div> ); 

技巧2.避免突变


考虑一个简单的待办事项清单应用程序。 在构造函数中,我们在状态中定义将待办事项存储在其中的变量。 在render()方法中,显示用于将案例添加到列表的表单。 现在考虑如何改变状态。

错误的选项导致数组突变:

 this.state.data.push(item); 

在这种情况下,数组确实发生了变化,但是React对它一无所知,这意味着将不会调用render()方法,并且不会显示我们的更改。 事实是,在JavaScript中,创建新数组或对象时,链接保存在变量中,而不是对象本身。 因此,在数据数组中添加一个新元素,我们将更改数组本身,但不会更改其链接,这意味着以状态存储的数据值将不会更改。

JavaScript的变化随时都可能遇到。 为避免数据突变,请对数组,filter()和map()方法使用扩展运算符,对于对象,请使用扩展运算符或Assign()方法使用扩展运算符。

 const newData = [...data, item]; const copy = Object.assign({}, obj); 

回到我们的应用程序,值得一提的是,更改状态的正确选项是使用setState()方法。 不要尝试在构造函数以外的任何地方直接更改状态,因为这与React意识形态相矛盾。

不要那样做!

 this.state.data = [...data, item]; 

也要避免状态突变。 即使使用setState(),在尝试优化时,变异也会导致错误。 例如,如果您将通过道具传递的变异对象传递给子级PureComponent,则此组件将无法理解接收到的道具已更改,并且不会重新渲染。

不要那样做!

 this.state.data.push(item); this.setState({ data: this.state.data }); 

正确的选项:

 const { data } = this.state; const newData = [...data, item]; this.setState({ data: newData }); 

但是,即使上面的选项也会导致细微的错误。 事实是,没有人能保证从状态接收数据变量到将新值写入状态之间所经过的时间,状态本身不会改变。 因此,您可能会丢失所做的某些更改。 因此,在需要使用状态变量的先前值更新状态变量的值的情况下,请执行以下操作:

如果以下条件取决于当前,则为正确的选项:

 this.setState((state) => { return {data: [...state.data, item]}; }); 

技巧3.模拟多页应用程序


您的应用程序正在开发中,并且在某个时候您意识到需要多页。 但是要做什么,因为React是一个单页面应用程序? 此时,您可能会想到以下疯狂的想法。 您决定将当前页面的标识符保持在应用程序的全局状态,例如使用redux存储。 要显示所需的页面,您将使用条件渲染,并在页面之间切换,以所需的有效负载调用action,从而更改store redux中的值。

App.js

 import React from 'react'; import { connect } from 'react-redux'; import './App.css'; import Page1 from './Page1'; import Page2 from './Page2'; const mapStateToProps = (state) => ({ page: state.page }); function AppCon(props) { if (props.page === 'Page1') { return ( <div className="App"> <Page1 /> </div> ); } return ( <div className="App"> <Page2 /> </div> ); } const App = connect(mapStateToProps)(AppCon); export default App; 

Page1.js

 import React from 'react'; import { connect } from 'react-redux'; import { setPage } from './redux/actions'; function mapDispatchToProps(dispatch) { return { setPageHandle: (page) => dispatch(setPage(page)), }; } function Page1Con(props) { return ( <> <h3> Page 1 </h3> <input type="button" value="Go to page2" onClick={() => props.setPageHandle('Page2')} /> </> ); } const Page1 = connect(null, mapDispatchToProps)(Page1Con); export default Page1; 

为什么这样不好?

  1. 该解决方案是原始自行车的示例。 如果您知道如何熟练地制造这种自行车并了解您的需求,那么我就不建议您。 否则,您的代码将是隐式的,令人困惑的和过于复杂的。
  2. 您将无法使用浏览器中的“后退”按钮,因为不会保存访问历史记录。

如何解决呢?

只需使用react-router即可 。 这是一个很棒的程序包,可以轻松地将您的应用程序转换为多页程序。

提示4.在何处放置api请求


有时,您需要向应用程序中的外部api添加请求。 在这里,您想知道:您需要在应用程序中的哪个位置执行请求?
目前,在安装React组件时,其生命周期如下:

  • 构造函数()
  • 静态getDerivedStateFromProps()
  • 渲染()
  • componentDidMount()

让我们按顺序分析所有选项。

在Constructor()方法中, 文档不建议您执行以下操作:

  • 通过分配this.state对象初始化内部状态。
  • 事件处理程序与实例的绑定。

对api的调用不包含在此列表中,所以让我们继续。

根据文档提供的getDerivedStateFromProps()方法在少数情况下(状态取决于props的更改)存在。 同样,不是我们的情况。

最常见的错误是在render()方法中执行api请求的代码的位置。 这导致一个事实,即您的请求成功执行后,您很可能会将结果保存在组件状态中,这将导致对render()方法的新调用,在该调用中将再次执行对api的请求。 因此,您的组件最终将无休止地呈现,这显然不是您所需要的。

因此,componentDidMount()方法是访问外部api的理想场所。

结论


代码示例可以在github上找到。

Source: https://habr.com/ru/post/zh-CN468799/


All Articles