Many of the real world operates in a responsive way. For example, we receive questions from others, and then respond and give corresponding answers. In the process of development, the author has also applied a lot of responsive design, accumulated some experience, and hope to be able to draw on the jade.
The main difference between reactive programming and common programming is that responsive programming can push(push
)The way to operate, rather than responsive programming ideas to pull(pull
)How it works. For example, an event is a very common reactive programming. We usually do this:
button.on('click', () => {
// ...
})
In the non responsive mode, it will be like this:
while (true) {
if (button.clicked) {
// ...
}
}
Obviously, non responsive design is not as good as responsive design in terms of code elegance or execution efficiency.
Event Emitter
Event Emitter
It is a very familiar event implementation for most people. It is very simple and practical, and we can use itEvent Emitter
Implement a simple responsive design, such as the following asynchronous search:
class Input extends Component {
state = {
value: ''
}
onChange = e => {
this.props.events.emit('onChange', e.target.value)
}
afterChange = value => {
this.setState({
value
})
}
componentDidMount() {
this.props.events.on('onChange', this.afterChange)
}
componentWillUnmount() {
this.props.events.off('onChange', this.afterChange)
}
render() {
const { value } = this.state
return (
<input value={value} onChange={this.onChange} />
)
}
}
class Search extends Component {
doSearch = (value) => {
ajax(/* ... */).then(list => this.setState({
list
}))
}
componentDidMount() {
this.props.events.on('onChange', this.doSearch)
}
componentWillUnmount() {
this.props.events.off('onChange', this.doSearch)
}
render() {
const { list } = this.state
return (
<ul>
{list.map(item => <li key={item.id}>{item.value}</li>)}
</ul>
)
}
}
We’ll find it useful hereEvent Emitter
There are a lot of shortcomings in the implementation of, we need to manually in thecomponentWillUnmount
To release resources. For example, when we need to aggregate multiple data sources in search:
class Search extends Component {
foo = ''
bar = ''
doSearch = () => {
ajax({
foo,
bar
}).then(list => this.setState({
list
}))
}
fooChange = value => {
this.foo = value
this.doSearch()
}
barChange = value => {
this.bar = value
this.doSearch()
}
componentDidMount() {
this.props.events.on('fooChange', this.fooChange)
this.props.events.on('barChange', this.barChange)
}
componentWillUnmount() {
this.props.events.off('fooChange', this.fooChange)
this.props.events.off('barChange', this.barChange)
}
render() {
// ...
}
}
Obviously, the development efficiency is very low.
Redux
Redux
In this paper, an event flow method is used to implement the responseRedux
Due toreducer
It must be a pure function, so the only way to implement responsive is in subscription or middleware.
If through subscriptionstore
BecauseRedux
Can not accurately get which data released changes, so only through the dirty check. For example:
function createWatcher(mapState, callback) {
let previousValue = null
return (store) => {
store.subscribe(() => {
const value = mapState(store.getState())
if (value !== previousValue) {
callback(value)
}
previousValue = value
})
}
}
const watcher = createWatcher(state => {
// ...
}, () => {
// ...
})
watcher(store)
This method has two disadvantages: one is the efficiency problem when the data is very complex and the amount of data is relatively large; the other is that ifmapState
If the function depends on the context, it is very difficult to do. stayreact-redux
Medium,connect
FunctionmapStateToProps
The second parameter of isprops
, which can be passed in through the upper layer componentsprops
To get the context you need, but the listener becomesReact
The component will be created and destroyed as the component is mounted and unloaded. If we want this responsive expression to be independent of the component, there will be a problem.
Another way is to monitor data changes in middleware. Benefit fromRedux
We can get the corresponding data changes by listening to specific events (actions).
const search = () => (dispatch, getState) => {
// ...
}
const middleware = ({ dispatch }) => next => action => {
switch action.type {
case 'FOO_CHANGE':
case 'BAR_CHANGE': {
const nextState = next(action)
//After this dispatch is completed, a new dispatch will be carried out
setTimeout(() => dispatch(search()), 0)
return nextState
}
default:
return next(action)
}
}
This method can solve most problems, but in theRedux
Middle, middleware andreducer
In fact, it is unreasonable to implicitly subscribe to all the actions, although it is totally acceptable without performance problems.
Object oriented response
ECMASCRIPT 5.1
Introducedgetter
andsetter
We can go throughgetter
andsetter
Implement a responsive.
class Model {
_foo = ''
get foo() {
return this._foo
}
set foo(value) {
this._foo = value
this.search()
}
search() {
// ...
}
}
//Of course, if there is no getter or setter, it can be implemented in this way
class Model {
foo = ''
getFoo() {
return this.foo
}
setFoo(value) {
this.foo = value
this.search()
}
search() {
// ...
}
}
Mobx
andVue
This method is used to implement responsive. Of course, we can use it without considering compatibilityProxy
。
When we need to respond to several values and then get a new value, theMobx
We can do this:
class Model {
@observable hour = '00'
@observable minute = '00'
@computed get time() {
return `${this.hour}:${this.minute}`
}
}
Mobx
Will be collected at run timetime
What values are dependent on and when these values change (triggersetter
)Recalculatetime
Is obviously better thanEventEmitter
The method is much more convenient and efficientRedux
Ofmiddleware
More intuitive.
But there is also a drawback here, based ongetter
Ofcomputed
Property can only be describedy = f(x)
But there are a lot of situations in realityf
Is an asynchronous function, then it becomesy = await f(x)
In this casegetter
It can’t be described.
In this case, we can pass theMobx
Providedautorun
To achieve:
class Model {
@observable keyword = ''
@observable searchResult = []
constructor() {
autorun(() => {
// ajax ...
})
}
}
Because the runtime dependency collection process is completely implicit, we often encounter a problem that unexpected dependencies are collected
class Model {
@observable loading = false
@observable keyword = ''
@observable searchResult = []
constructor() {
autorun(() => {
if (this.loading) {
return
}
// ajax ...
})
}
}
Obviously hereloading
It’s not supposed to be searchedautorun
Collected, in order to deal with this problem, there will be some extra code, and redundant code is prone to error.
Alternatively, we can manually specify the required fields, but in this way, we have to add some additional operations:
class Model {
@observable loading = false
@observable keyword = ''
@observable searchResult = []
disposers = []
fetch = () => {
// ...
}
dispose() {
this.disposers.forEach(disposer => disposer())
}
constructor() {
this.disposers.push(
observe(this, 'loading', this.fetch),
observe(this, 'keyword', this.fetch)
)
}
}
class FooComponent extends Component {
this.mode = new Model()
componentWillUnmount() {
this.state.model.dispose()
}
// ...
}
And when we need to do something about the timeline,Mobx
For example, we need to delay the search by 5 seconds.
In the next blog post, we will introduceObservable
The practice of handling asynchronous events.
Original address: https://tech.youzan.com/react…