为什么我决定回到Vanilla React (Why I decided to go back to vanilla React)
I’ve done some prototype work to demonstrate the benefits of a data access layer between client logic and persistence. Along the way, I’ve become a big fan of GraphQL. Though I like React, it was not the low(er)-code approach I had hoped it would be (though, hey: no jQuery!). I tried mixing in Redux to simplify coding further, but there were disappointments there as well.
我已经做了一些原型工作,以演示客户端逻辑和持久性之间的数据访问层的好处。 一路走来,我已经成为GraphQL的忠实拥护者。 尽管我喜欢React,但这并不是我希望的低代码方法(尽管,嘿:没有jQuery!)。 我尝试在Redux中进行混合以进一步简化编码,但是那里也令人失望。
React is conceptually simple: a component may contain state and receive props. React will monitor changes in state and will re-render that component and any child components that might be affected by the state change. State is passed to children though props (element attributes). Some built-in React component methods are called in the process, each of which may be overridden as needed (to avoid, say, unnecessary re-renders).
React从概念上讲很简单:一个组件可以包含state并接收props 。 React将监视状态更改,并将重新呈现该组件以及任何可能受状态更改影响的子组件 。 通过道具(元素属性)将状态传递给孩子。 在过程中会调用一些内置的React组件方法,可以根据需要重写每个方法(以避免不必要的重新渲染)。
One of the first resources I turned to when learning React was Bucky Robert’s series. Bucky does a good job in explaining concepts simply and informally. You get the gist of how React works, which is what you need when getting started.
在学习React时,我转向的第一个资源是Bucky Robert的系列 。 Bucky在简单和非正式地解释概念方面做得很好。 您将了解React的工作原理,这是入门时需要的。
Thus forearmed, I wrote some React code. At first this went very well. Yet as my hierarchy of components grew more complex, tracking the relationship hierarchy of each component, along with all the props being passed, became confusing.
因此,我编写了一些React代码。 起初,这进行得很好。 但是,随着我的组件层次结构变得越来越复杂,跟踪每个组件的关系层次结构以及所传递的所有道具变得令人困惑。
When learning React, it helps to make a clear distinction between presentational components and container components. Presentational components are the elements shown on the page. Container components are the components that maintain state for their child components. Container Components may be presentational, container, or both. Containers are smart and have state logic. Presentation components are dumb and are mostly templated HTML that handle the presentation of passed-in props.
在学习React时,它有助于在表示性组件和容器组件之间进行清晰区分。 呈现组件是页面上显示的元素。 容器组件是为其子组件维护状态的组件。 容器组件可以是外观的,容器的或两者兼有。 容器很智能,并且具有状态逻辑。 呈现组件很笨,大部分都是模板HTML,用于处理传入的道具的呈现。
At first, it can be hard to see what components influence each other and share state and thus need to belong in the same container. You will need to shuffle around state and redo the property passing, as it becomes clearer what components are to work together. This is what is what is referred to as “refactoring”.
首先,很难看到哪些组件会相互影响并共享状态,因此需要属于同一容器。 您将需要重新整理状态并重做传递的属性,因为可以更清楚地了解哪些组件可以协同工作。 这就是所谓的“ 重构 ”。
道具,道具和更多道具 (Props, props, and more props)
All changes go through properties. Most tutorials show this by passing each prop by name from the root container component on down through all the children, where each child component picks the properties it wants and ignores the rest.
所有更改都通过属性。 大多数教程通过将名称从根容器组件向下传递到所有子组件的名称来展示这一点,其中每个子组件都会选择所需的属性,而忽略其余属性。
Let’s take an example from React’s own docs:
让我们以React自己的文档为例:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
The Welcome component takes a set of properties or props. It uses the prop called name to display a personalized welcome message. The containing component is an anonymous <div>. It passes names to the Welcome component for three people.
Welcome组件带有一组属性或道具。 它使用名为name的道具来显示个性化的欢迎消息。 包含的组件是匿名<div>。 它将名称传递给三个人的Welcome组件。
That’s all well and good. But what happens when you want to display not just (first) name, but last name, address, email, and phone number in the Welcome component?
一切都很好。 但是,当您不仅要在“ 欢迎”组件中显示(名字),还要显示姓氏,地址,电子邮件和电话号码时,会发生什么情况?
function Welcome(props) {
return <div>
<h1>Hello, {props.first_name} {props.last_name}</h1>
<ul>
<li> email: {props.email} </li>
<li> phone: {props.phone} </li>
<li> address: /* mercifully omitted */ </li>
</ul>
</div>;
}
function App() {
return (
<div>
<Welcome first_name="Sara" last_name="Smith" email="...", phone="...", address={/*address object*/}/>
<Welcome first_name="Cahal" last_name="Murthi" email="...", phone="...", address={/*address object*/}/>
<Welcome first_name="Edite" last_name="Franco" email="...", phone="...", address={/*address object*/}/>
</div>
);
}
Explicitly passing props is noisy. What’s more, if the Welcome component is a composite of several other components, each with their own set of needed properties, you have to pass those to the Welcome component, too.
明确传递道具很吵。 此外,如果Welcome组件是其他几个组件的组合,每个组件都有自己的一组所需属性,那么您也必须将它们传递给Welcome组件。
Props are not only data, but methods as well. Props are immutable by convention.
道具不仅是数据,而且还是方法。 根据惯例,道具是不可变的。
If any child wants to change a property, it should be done via a passed-in set method from some container that holds state. The child calls the state set method, updates the state and generates new prop values. Then each child is notified of the property changes. The child that’s doing the state mutation doesn’t know which container holds the state, but doesn’t need to. It calls the set method it is given from some anonymous parent container.
如果任何孩子想更改属性,则应通过传入保存状态的容器中的set方法来完成。 子代调用状态设置方法,更新状态并生成新的prop值。 然后,将属性更改通知每个孩子。 进行状态突变的孩子不知道哪个容器保存状态,但是不需要。 它调用set方法,它是从某个匿名父容器中获得的。
Here’s another example from the React docs:
这是React文档中的另一个示例:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
Although in this case the button has direct access to the state, the common case is that state is passed as properties to child Button presentational component, with an additional set method to change isToggleOn in this component’s state.
尽管在这种情况下,按钮可以直接访问状态,但常见的情况是将状态作为属性传递给子Button表示组件,并使用其他set方法更改该组件的状态中的isToggleOn。
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() => <Button
onclick=handleClick.bind(this)
isToggleOn=this.state.isToggleOn />;
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
Arrggh,重构 (Arrggh, refactoring)
So you do all this property propagation through all the child components and everything is beautiful. Then you add one more component, and realize that it relies on some state that is not in the container you want to put the new component in.
因此,您可以通过所有子组件进行所有这些属性传播,并且一切都很漂亮。 然后,再添加一个组件,并意识到它依赖于您要放入新组件的容器中没有的某种状态。
Let’s start with a simple List and Details application:
让我们从一个简单的“列表和详细信息”应用程序开始:
As items are chosen in the List, notification is sent to the Container via a mutator that was sent as a prop, and the Container’s state is changed. This causes both List and Details to re-render. Details get notified of the item selected in the List as part of that re-render operation, and updates with new item information.
在列表中选择项目后,通知会通过作为道具发送的变体发送到容器,并且容器的状态会更改。 这将导致列表和详细信息都重新呈现。 作为重新渲染操作的一部分,详细信息将通知列表中选定的项目,并使用新的项目信息进行更新。
Now we later decide that we want to add a Filter to the List. We add a new container to manage the Filter state such as a radio control. When a Filter is changed, it updates the Subcontainer’s state, which causes the List to re-render. The outermost container now contains the Subcontainer instead of the List. It still contains the Details component, but the state management of the selected List item remains the same. The Container knows nothing of Filter.
现在,我们稍后决定要向列表添加过滤器。 我们添加了一个新容器来管理Filter状态,例如无线电控件。 更改过滤器后,它将更新子容器的状态,这将导致列表重新呈现。 现在,最外面的容器包含“子容器”而不是“列表”。 它仍然包含“详细信息”组件,但是所选“列表”项目的状态管理保持不变。 容器不了解过滤器。
Nothing much as changed. The Container now has a Subcontainer rather than a List, but the same props are passed to the new child component. Each container has its own state that it manages.
没什么变化。 现在,容器具有子容器而不是列表,但是相同的道具被传递到新的子组件。 每个容器都有自己管理的状态。
However… later on we realize that knowing which Filter is applied will affect what Details we display, but because Filter is a sibling to Details, Details will have no access to the state of the Filter. So now the choice is:
但是,……稍后,我们意识到知道应用了哪个过滤器将影响我们显示的详细信息,但是由于过滤器是“详细信息”的兄弟,因此“详细信息”将无法访问过滤器的状态。 所以现在的选择是:
- have the List items contain information about what they are filtered by 使列表项包含有关其筛选依据的信息
- push the Filter state up from the Subcontainer to the Container 将筛选器状态从子容器上移到容器
This is the React refactoring. Anything that shares state has to be in the same container (at some level). There’s nothing wrong with the concept, but you never get it right the first time. Nor do components stay in one place for very long as the application evolves.
这是React重构。 共享状态的任何内容都必须位于同一容器中(某种程度上)。 这个概念没有错,但是您永远不会第一次就正确。 随着应用程序的发展,组件也不会停留在一处。
载水 (Carrying Water)
Containers are facilitators, passing knowledge between child components. When the facts change, the components get redrawn. But they are nosey as well as noisy facilitators. They know everything about what their children are interested in, but that doesn’t make them good parents. I’ve written about this before, where such knowledge is not always a good thing.
容器是促进者,在子组件之间传递知识。 当事实发生变化时,组件将重新绘制。 但是他们既好听又好听。 他们了解孩子感兴趣的一切,但这并不能使他们成为好父母。 我之前已经写过有关此的文章 ,其中的知识并不总是一件好事。
解决方案1:Redux (Solution 1: Redux)
One solution is to not have so many states. Why not just have one? Well, if you recall, every change in state will notify children that some property has changed. It’s up to the child component to know if that property affects what they are displaying. But the notification is sent regardless.
一种解决方案是不具有太多状态。 为什么不只有一个? 好吧,如果您还记得的话,每次状态更改都会通知孩子某些财产已更改。 由子组件决定该属性是否影响它们显示的内容。 但是无论如何都会发送通知。
Rather than the container assuming it knows about which properties are passed to children, why not have an inversion-of-control where children say which properties they’re interested in, and so subscribe to those state changes and those state changes only.
而不是容器假设容器知道将哪些属性传递给子代,为什么不进行控制反转,子代说出他们感兴趣的属性,因此仅订阅那些状态更改,而仅订阅那些状态更改。
一种统治所有国家的状态 (One state to rule them all)
So that’s where Redux comes in. It provides all components with only one state, maintained independently of, but accessible by, all React components.
这就是Redux的用武之地。它为所有组件提供仅一种状态,并独立于所有React组件但可由所有React组件访问。
Redux introduces several new pieces. First is the state container, called the Store. The Store is connected to your app via a Provider. These two are “set and forget”. Once a few lines of code is written, you never touch it again.
Redux引入了一些新功能。 首先是状态容器,称为存储。 商店通过提供商连接到您的应用程序。 这两个是“设定并忘记”。 一旦编写了几行代码,您就再也不会碰它。
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import RootReducer from './app/reducers'
import App from './app/app'
const store = createStore(RootReducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
The other two parts are a little more involved: Actions and Reducers. An event such as a keystroke or database query result creates an Action. The Action is then dispatched to be handled by some Resolver, based on the Action type. If you read my previous series on Seneca microservices, you will notice how Redux Actions are similar to Seneca patterns, and Reducers are similar to Seneca Actions.
其他两个部分则涉及更多:动作和简化器。 诸如击键或数据库查询结果之类的事件将创建一个动作。 然后,根据Action类型将Action调度为由某些解析程序处理。 如果您阅读了我以前有关Seneca微服务的系列文章,您会注意到Redux Actions与Seneca模式相似,而Reducer与Seneca Actions类似。
Reducers, once triggered, will modify Redux State according to data in the Action message. So a component can kickoff an Action which might invoke a database query or file fetch or whatever, the results of which are attached to the Action as payload and then dispatched to the cloud of Reducers, one of which will (hopefully) pick up where the Action left off and modify part of the State so that components listening to parts of it have the opportunity to be re-rendered.
还原器一旦触发,将根据操作消息中的数据修改Redux状态。 因此,组件可以启动一个Action,该Action可能调用数据库查询或文件提取或其他操作,其结果作为有效负载附加到Action上,然后分派到Reducers的云中,其中一个(希望)将在停止了行动并修改了部分国家,以便侦听该国部分内容的人员有机会重新呈现。
There is no passing of props from containers to children, but props are still involved.
道具没有从容器传递到儿童的地方,但仍然涉及道具。
import { connect } from 'react-redux'
import { setVisibility } from '../actions'
import Popup from '../components/Popup'
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.toggle === state.visibilityToggle
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibility(ownProps.toggle))
}
}
}
const Toggle = connect(
mapStateToProps,
mapDispatchToProps
)(Popup)
export default Toggle
In the above, a Popup component is tied to State via property mappings using Redux API methods mapDispatchToProps and mapStateToProps. This code would most likely be included in a container of the Popup component. More on that later.
在上文中,使用Redux API方法mapDispatchToProps和mapStateToProps通过属性映射将Popup组件绑定到State。 此代码很可能包含在Popup组件的容器中。 以后再说。
The traditional way this is organized is that you have Actions in an /actions folder. Usually an index.js is in that folder which imports all the actions so that they can be imported in one line in the dependents that need them. Reducers are in a /reducers folder. Components are in a /components folder or split between “presentational” /components and /containers. And the app will be in the root folder.
传统的组织方式是/ actions文件夹中有Actions。 通常,index.js位于该文件夹中,该文件夹将导入所有操作,以便可以在需要它们的依赖项中一行导入它们。 减速器位于/ reducers文件夹中。 组件位于/ components文件夹中,或在“代表性” / components和/ containers之间拆分。 该应用程序将位于根文件夹中。
虽然所有这些接线 (All this wiring, though)
So you wind up with action files with constants that identify the Actions in the file, and Reducers that use those constants to receive and handle Action types. Every component dealing with state is wired to fire those actions, along with properties that are affected by state change.
因此,您将获得带有用于标识文件中动作的常量的动作文件,以及使用这些常量接收和处理动作类型的Reducers。 连接状态的每个组件都将触发这些动作,以及受状态更改影响的属性。
That’s all very well and good, until you start building components and things don’t work right and you wonder stuff like:
一切都很好,直到您开始构建组件并且一切都无法正常进行,并且您想知道以下内容:
- Did I remember to define the action? 我还记得定义动作吗?
- Did I remember to export the action? 我还记得导出动作吗?
- Did I define the reducer? 我定义了减速器吗?
- Did I include the action constant in my component? 我是否在组件中包含了动作常量?
- Did I import it into my reducer? 我是否将其导入到我的减速器中?
- Did I make a typo? 我打错字了吗?
- What was the name of that file that had that thing that I forgot now? 我现在忘了那个东西的那个文件叫什么名字?
Yeesh! You wind up doing a lot of grepping through your code, assuming you can remember what it is you’re grepping for. One solution to the problem is to make Actions and Reducers co-local. They are codependent, so defining both in a common file makes sense.
! 假设您可以记住自己要做什么,那么您在代码中会做很多重复工作。 解决该问题的一种方法是使Actions和Reducers局部存在 。 它们是相互依赖的,因此在一个公共文件中定义它们都是有意义的。
解决方案2:回到使用ES6进行响应 (Solution 2: Back to React with ES6)
As I started to get a handle on Redux, I noticed others using some techniques that, had I thought of them at the time, would have made dealing with vanilla React a lot easier. So, with Redux being no less low-code than React alone (remember, I’m just working on a simple prototype), I dump Redux.
当我开始使用Redux时,我注意到其他人使用了某些技术,如果我当时想到它们,本来可以轻松处理Vanilla React。 因此,由于Redux的低代码比单独的React低(请记住,我只是在一个简单的原型上工作),所以我转储了Redux。
传播和休息 (Spread and rest)
In Carrying Water, I mention the difference between active and passive carrying of data-in-transit. The former is bad, but the latter is acceptable, because it avoids tight coupling. Data is merely passed-through to the intended recipient. It’s the difference between the Post Office opening a package and re-packaging everything in it in their own packages, versus just sending the one package on its way.
在“ 载水”中,我提到了主动和被动传输途中数据的区别。 前者是不好的,但后者是可以接受的,因为它避免了紧密耦合。 数据仅传递给预期的接收者。 邮局打开包裹并将包裹中的所有东西重新包装成自己的包裹,与仅按其方式发送一个包裹之间的区别是。
By using the object spread operator, it is possible to pass properties along to children without explicit reference to the properties themselves. While this still “carries water” from container to subcomponents, it does so in an implicit way. All the container knows is it has props to send down. If it has state, it sends those down, too.
通过使用对象传播运算符 ,可以将属性传递给子级而无需明确引用属性本身。 尽管这仍然从容器“携带水”到子组件,但它是以隐式方式进行的。 容器只知道它有道具可以下送。 如果它具有状态,它也会将其发送出去。
It should be mentioned, though, that the spread operator for objects is not yet an official part of ECMAScript. The Babel transpiler supports it, if it is configured to do so.
但是,应该指出的是,对象的散布运算符尚未成为ECMAScript的正式组成部分。 Babel编译器支持它(如果已配置)。
{
"presets": [
"latest",
"react"
],
"plugins": ["transform-object-rest-spread", "syntax-object-rest-spread"]
}
挑选物业 (Picking off properties)
One concern is that of passing too much information down to child components. One way to avoid that is for higher-up containers and components to “pick off” the properties they are interested in, and only pass down the rest. This can be done through object destructuring:
一个问题是将过多的信息传递给子组件。 避免这种情况的一种方法是让较高层的容器和组件“拾取”他们感兴趣的属性,而只传递其余部分。 这可以通过对象分解来完成:
var { checked, ...other } = props;
Here, the prop checked is pulled from the other props, and then other is passed down (without the checked prop [example from the link above]):
在这里,将选中的道具从其他道具中拉出,然后再向下传递(没有选中的道具[来自上面的链接的示例]):
function FancyCheckbox(props) {
var { checked, ...other } = props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
// `other` contains { onClick: console.log } but not the checked property
return (
<div {...other} className={fancyClass} />
);
}
React还是Redux? (React or Redux?)
When building a prototype to demonstrate some concept or feature, simpler is better. React is conceptually easier to deal with. Redux has a lot going on under the hood, and it has been noted how fine-grained the actions can become. Need to show a spinner? Fire off an action!).
在构建原型来演示某些概念或功能时,越简单越好。 从概念上讲,React更易于处理。 Redux在幕后进行了很多工作,并且已经注意到动作可以变得多么细粒度。 需要展示微调器吗? 开火!)。
Tooling surrounding Redux is improving, and will simplify the overhead of maintaining actions, reducers, mapStateToProps, and matchDispatchToProps, by using more declarative stitching together of the pieces and using implicit rules for mundane wiring.
围绕Redux的工具正在改进 ,并将通过使用更多的声明性拼接方式以及对平凡的布线使用隐式规则来简化维护动作,reduce,mapStateToProps和matchDispatchToProps的开销。
翻译自: https://www.freecodecamp.org/news/the-road-to-redux-and-back-d9987c7bb894/