Application of React Stateful Component and key Attribute
Stateful components
When building React components, a common scenario is that some rendering data need to be computed by props, which can be implemented in two ways as follows:
- Method 1: Pros is processed in the constructor, and state is used to carry the final rendering data.
- Method 2: Calculate directly in render and render with the result of calculation.
Both methods have their advantages and disadvantages:
- Method 1 is often implemented in conjunction with component WillReceive Props. Why? Once the props used for computing change, the state needs to be updated synchronously.
- Method 2 needs to calculate every render, so it can't ignore its influence on performance.
Following this line of thought, how to build a complex stateful component? There are already too many best practices on the Internet, each of which has its own merits. I won't repeat them here. In my opinion, component design is actually a trade-off, the first consideration is the rationality of module decomposition and the maintainability of code.
It is emphasized here that in React components, once you need to do some setState operations in component WillReceive Props, you should first consider whether props or state need to be redesigned. Because in well-designed React components, the information contained in props and state should be orthogonal.
key attribute
If a component is a complex stateful component, this is unavoidable, and you want to rebuild the component every time you map it (for example, the component corresponds to the same routing, the only difference is the parameter), you can consider trying the key attribute.
Why can the key attribute solve this problem? Only by interpreting its realization method can we get the most correct understanding.
ReactChildReconciler is the concrete implementation of React operation children, including initialization, update and destruction. Mainly focus on updateChildren's logic, key attributes change when the logic processing source code excerpts as follows:
updateChildren: function(prevChildren, nextChildren, ...) { ... for (name in nextChildren) { ... // The child must be instantiated before it's mounted. var nextChildInstance = instantiateReactComponent(nextElement, true); nextChildren[name] = nextChildInstance; // Creating mount image now ensures refs are resolved in right order // (see https://github.com/facebook/react/pull/7101 for explanation). var nextChildMountImage = ReactReconciler.mountComponent( nextChildInstance, transaction, hostParent, hostContainerInfo, context, selfDebugID, ); mountImages.push(nextChildMountImage); ... } // Unmount children that are no longer present. for (name in prevChildren) { ... prevChild = prevChildren[name]; removedNodes[name] = ReactReconciler.getHostNode(prevChild); ReactReconciler.unmountComponent( prevChild, false /* safely */, false /* skipLifecycle */, ); ... } },
When the key changes, the prevChild is deleted and a new nextChild is created. So the key attribute can trigger the destruction and reconstruction of components at the right time.
react-router
Back to the specific application scenario, when using react-router, route components are usually more complex stateful components. How to jump freely between routing pages and automatically trigger component reconstruction is a more difficult problem, because if it is the same set of mapping components, only re-render. With the key, we can build the key on the route component according to the params, so the above problem can be solved.
Meanwhile, the method of uploading props to route component is attached for reference.
class App extends Component { static createElement = (Component, ownProps) => { const { userId } = ownProps.params; switch (Component) { case UserDashboard: return <Component key={userId} {...ownProps} />; default: return <Component {...ownProps} />; } }; render() { return ( <Provider store={store}> <Router createElement={App.createElement} history={syncHistoryWithStore(hashHistory, store)}> <Route path="/" component={Home}> <IndexRoute component={Index}/> <Route path="users/:userId" component={UserDashboard}/> </Route> </Router> </Provider> ) } }
Method of adding key attributes to components
The method that adds key attribute to the route component above executes the createElement method every time it changes, which is too intrusive. If you don't like it, here's a more general way to add key attributes to components.
If the component is written as
export default class Demo extends React.Component {}
Then you can add key attributes in the following way
class Demo extends React.Component {} export default function (props) { return (<Demo {...props} key={...} />); }
What if it's a component connected to Redux, such as:
class Demo extends React.Component {} export default connect()(Demo);
Modifications are simple, too.
class Demo extends React.Component {} const FinalDemo = connect()(Demo); export default function (props) { return (<FinalDemo {...props} key={...} />); }
The method of adding key attributes to a component described above is simpler and converges changes within the component.
At the same time, we can use this method to add other attributes to the component, such as: url, whether it immediately associates with a broader applicable scenario, in fact, I generally use this method.
class Demo extends React.Component {} const FinalDemo = connect()(compose(AsyncDecorator)(Demo)); export default function (props) { return (<FinalDemo {...props} url={...} />); }
Using the idea of functional programming, through compose and Decorator, the componentized development method is realized, which achieves a single responsibility, and the code structure becomes clearer.
If you have a similar way of componentization, you are welcome to send information exchange!
epilogue
Although the key attribute has so much energy, it can not be abused and needs to be weighed. After all, reconstruction is a relatively resource-consuming behavior. This article is just to provide a solution to the problem, the perfect solution still needs to isolate the logic in the design, avoid some anti-patterns from the source, and achieve real componentization.