Control — this concept is crucial in programming. For example, the “battle” between the “wheel” encapsulation layer and the business consumption layer for control is an interesting topic. This is no exception in the React world. On the surface, of course, we want the wheel to control as many things as possible: the more logic the abstraction layer handles, the less you care about business calls and the easier it is to use. But some designs are “afraid to cross the line”. The tug of control between the wheel and the business is interesting.
At the same time, the ability to control components is closely related to component design: Atomic components such as Atomic components are favored; On top of atomic components, there are Molecules components. Whether molecules or atoms, there is a reason to solve business problems.
In this article, I will use the React framework as a backdrop to share some of my thoughts and conclusions about control during development. If you don’t use React, it still doesn’t interfere with reading in principle.
Before I begin, I’d like to introduce a book to you.
Since last year, I have been co-writing with yan Haijing, a well-known technology guru. This year, the book we worked on together ** React State Management and Isometric Combat ** was finally published! The core of this book is the React stack. On the basis of introducing the use of React, it analyzes the idea of Redux from the source level, and focuses on the architecture mode of server-side rendering and homogeneous application. The book contains many project examples that not only open the door to the React stack, but also improve the reader’s overall understanding of the frontier.
If you are interested in the book content or the following content, please support us! More at the end of this article, don’t go away!
Start with controlled and uncontrolled components
When we first entered the React gate, the first thing we learned about the concept of control was controlled components and uncontrolled components. These two concepts are often associated with forms. In most cases, controlled components are recommended for state control of forms, input fields, and so on. In controlled components, data such as forms is handled by the React component itself. As opposed to a controlled component, the Dom controls the data of the form itself. Here is a typical uncontrolled component:
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
Copy the code
React has no direct control over the state of uncontrolled components or user input. Instead, it relies on the native capabilities of form tags to interact. If you make the above uncontrolled component a controlled component, the code is also simple:
class NameForm extends React.Component { state= {value: ''} handleChange = event => { this.setState({value: event.target.value}); } handleSubmit = event => { alert('A name was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ) } }Copy the code
The React component controls form values and behavior, making development easier.
This is of course a very basic concept, which brings up the topic of control. Keep reading.
UI “Wheels” with Control Props mode
The sample I described above is what I call “narrowly controlled and uncontrolled” components. Broadly speaking, I consider fully uncontrolled components to be functional or stateless components that have no internal states and only accept props. Its rendering behavior is completely controlled by external props passed in, with no “autonomy” of its own. Such components achieve good reusability and have good testability.
However, in UI “wheel” design, “semi-autonomous” or “not fully controlled” components can sometimes be a better choice. We call this the “Control Props” pattern. In simple terms, the component has its own state. When no related PORps are passed in, it uses its own state stateA to complete the rendering and interaction logic. When the component is invoked, if the props are passed in, control is handed over, and the business consumption level controls its behavior.
After studying a number of community UI “wheels,” I found that downShift, a component library written by Kent C. Dodds and used at paypal, widely adopted this pattern.
A simple example is a Toogle component that is called by the business side:
class Example extends React.Component {
state = {on: false, inputValue: 'off'}
handleToggle = on => {
this.setState({on, inputValue: on ? 'on' : 'off'})
}
handleChange = ({target: {value}}) => {
if (value === 'on') {
this.setState({on: true})
} else if (value === 'off') {
this.setState({on: false})
}
this.setState({inputValue: value})
}
render() {
const {on} = this.state
return (
<div>
<input
value={this.state.inputValue}
onChange={this.handleChange}
/>
<Toggle on={on} onToggle={this.handleToggle} />
</div>
)
}
}
Copy the code
The effect is as follows:
We can use the input box to control the Toggle component state switch (input “on” to activate the state, input “off” to turn grey). At the same time, we can also click the mouse to switch, and the content of the input box will change accordingly.
Consider this: For the UI component Toggle, its state can be controlled by the business caller, which gives convenience to consumption at the usage level. In business code, either Input or any other component can control its state, and we have complete control when we call it.
Also, if you do not pass the props value when calling the Toggle component, the component will still function. As follows:
<Toggle> {({on, getTogglerProps}) => ( <div> <button {... getTogglerProps()}>Toggle me</button> <div>{on ? 'Toggled On' : 'Toggled Off'}</div> </div> )} </Toggle>Copy the code
When the Toggle component switches the state, it maintains the internal state by itself to achieve the switching effect. At the same time, it outputs the state information of the component externally through the Render Prop mode.
Let’s look at the source code of Toggle (some parts have been deleted) :
const callAll = (... fns) => (... args) => fns.forEach(fn => fn && fn(... args)) class Toggle extends Component { static defaultProps = { defaultOn: false, onToggle: () => {}, } state = { on: this.getOn({on: this.props.defaultOn}), } getOn(state = this.state) { return this.isOnControlled() ? this.props.on : state.on } isOnControlled() { return this.props.on ! == undefined } getTogglerStateAndHelpers() { return { on: this.getOn(), setOn: this.setOn, setOff: this.setOff, toggle: this.toggle, } } setOnState = (state = ! this.getOn()) => { if (this.isOnControlled()) { this.props.onToggle(state, this.getTogglerStateAndHelpers()) } else { this.setState({on: state}, () => { this.props.onToggle( this.getOn(), this.getTogglerStateAndHelpers() ) }) } } setOn = this.setOnState.bind(this, true) setOff = this.setOnState.bind(this, false) toggle = this.setOnState.bind(this, undefined) render() { const renderProp = unwrapArray(this.props.children) return renderProp(this.getTogglerStateAndHelpers()) } } function unwrapArray(arg) { return Array.isArray(arg) ? arg[0] : arg } export default ToggleCopy the code
The key is that the isOnControlled method inside the component determines if a property named ON is passed in: if so, it uses this.props. On as the state of the component, and its own this.state.on to manage the state. The Render Prop mode is also used in the Render method, which is not discussed in this article, but can be found in the community as well as in my new book.
Taking stock, the Control props pattern reflects a typical control problem. Such “semi-autonomy” is perfectly suited to business needs and is more flexible and efficient in component design.
Redux asynchronous state management and control
When it comes to control, you can’t miss a state management tool like Redux. Redux’s design handles all aspects of control well, but we’ll focus on asynchrony here, and more on my new book.
Redux handles async, and is best known for middleware like Redux-Thunk, which was written by Dan himself and is documented by Amway on the official Redux documentation. Like all other middleware, it controls the process from action to reducer, so that a function type action can be directly dispatched when business is used. The implementation code is also very simple:
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); export default thunk;Copy the code
However, it was quickly argued that such a solution resulted in a lack of business code simplification due to insufficient control in the middleware implementation. We still need to follow the traditional Redux steps: compile actions, action creactor, reducer…… As a result, middleware solutions with larger control granularity emerge.
The action type is controlled by the Redux-Promise middleware, which restricts a business party to execute resolve when it dispatches an asynchronous action whose payload attribute needs to be a Promise object. The middleware triggers an action of the same type, sets the payload as the promise value, and sets the action.status property to “success”.
export default function promiseMiddleware({ dispatch }) { return next => action => { if (! isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload .then(result => dispatch({ ... action, payload: result })) .catch(error => { dispatch({ ... action, payload: error, error: true }); return Promise.reject(error); }) : next(action); }; }Copy the code
This design is completely different from Redux-Thunk in that it controls the Thunk process within the middleware itself, so that the third-party wheels do more and are therefore more concise when making business calls. We just need to write the action normally:
Dispatch ({type: GET_USER, payload: http.getuser (userId) // Payload is a promise object})Copy the code
Compare redux-Thunk with redux-Thunk, where the business has more control than the wheel, and you have to implement the three lines above:
dispatch(
function(dispatch, getState) {
dispatch({
type: GET_USERE,
payload: userId
})
http.getUser(id)
.then(response => {
dispatch({
type: GET_USER_SUCCESS,
payload: response
})
})
.catch(error => {
dispatch({
type: GET_DATA_FAILED,
payload: error
})
})
}
)
Copy the code
Of course, more control over Redux-Promise brings simplicity on the one hand, but on the other, less control over the business and a loss of autonomy. For example, if you want to implement Optimistic updates, that’s hard to do. See Issue #7 for details
To balance this, in between the middleware of redux-Thunk and Redux-Promise, the two extremes of the control philosophy, there is an intermediate state middleware: Redux-promise-middleware, which is similar to Redux-Thunk and has similar control granularity, but is milder and more progressive in action processing, It will dispatch XXX_PENDING, xxx_depressing and XXX_REJECTED actions at appropriate times. That is to say, the middleware increases the level of communication with the external third party on the basis of controlling more logic. This is a big pity, XXX_FULFILLED, XXX_REJECTED, please feel the difference carefully.
Controllism and minimalism in state management
After understanding the control problem in asynchronous state, we analyze it from the global perspective of Redux. For internal sharing, I summarized the common features of the State management library based on Redux encapsulation as slide:
The above four points are all redux-based simplifications of the related libraries, but the last three are very interesting, and they are invariably related to control. Represented by Rematch, it is no longer the middleware that processes actions to reducer, but completely controls the process of Action Creator, Reducer and unicom.
Specifically,
-
The business operator no longer needs to display the action type declaration, which is generated directly from the reducer function name. If the reducer name is increment, then action.type is increment.
-
Control Reducer and Action Creator into one, state management has never been so simple and efficient.
I call this practice controllism or minimalism, and it’s much more thorough than a state-management library like Redux-Actions. For specific ideas, please refer to the article written by Shawn McKay, whose introduction is quite sufficient and I will not repeat it here.
Conclusion: code farming and control
Control is ultimately a design idea, which is the clash and collision between the third party class library and business consumption. It has nothing to do with languages or frameworks. This article uses React as an example. It has nothing to do with abstract categories. This paper has analyzed UI abstraction and state abstraction respectively. Control is closely related to the coder, it directly determines our programming experience and development efficiency.
But in the initial stage of programming, excellent control design is difficult to achieve overnight. We will all grow if we get involved in front-line development, understand our business needs, summarize a lot of best practices, and look at the best in the community and analyze the best open source work.
Finally, front-end learning is endless, I hope to make progress together with every technology enthusiast, you can find me in Zhihu!
Happy coding!
Happy coding!
React State Management and Isomer Combat was jointly polished by Yan Haijing, a well-known technology guru in the front end, and condensed our accumulation and experience in the process of learning and practicing React framework. ** In addition to the React framework usage introduction, focuses on the analysis of state management and server rendering isomorphism application content. ** has also absorbed a large number of excellent ideas from the community and summarized and compared.
The book was written by Baidu vice President Shen Dou, Baidu senior front end engineer Dong Rui, Ruan Yifeng, a well-known expert on JavaScript language, Wolf Shu, a nojue advocate, JustJavac, the founder of Flarum Chinese community, Xiaojue, a technical expert on sina mobile, gu Yiling, a senior front-end engineer of Baidu, and other experts in the front-end area of Jue.
Interested readers can click here for details. You can also scan the QR code below to buy. Thank you again for your support and encouragement! Please criticize and correct!