0%

VueJS is using similar structure like EventEmitter for child-to-parent communication with its $emit API. When parent component wants to know certain lifecycle events that child component is in, we can simply trigger an event in related hook:


const Child = {
mounted () {
this.$emit('child-mounted')
}
}

<parent @child-mounted="doSomething"><child /></parent>

With that callback, we can do numerous things like managing auto-focus behavior or getting child component layout sizing (width, height etc.).

However, if the child component is from 3rd party library which does not provide such callback, we can still tap into its lifecycle in this way:

<parent @hook:mounted="doSomething">
<child />
</parent>

Strangely, this is not listed in any of the official document. If you are interested, check the source code

So literally it is just syntax-sugar and will trigger event in this special hook: namespace.

In every Javascript 101 class, closure is always an indispensable part. I wonder how many beginners will stumble into this tiny piece of code:

for (var i > 0; i < array.length; i++) {
button.onclick = function(){alert(i)}
}

But we are not revisiting this intrigue yet important concept in Javascript today. In terms of closure in this article, we are referring the way we store a local variable in our components for later use, like registering a resize function and call removeEventListener for cleanup before component being destroyed.

In VueJS or React class-based component, this is easy like a breeze, since we always have this reference in component, which can be accessed in all lifecycle events like mounted/beforeDestroy (VueJS) and componentWillMount/componentWillUnmount (React)


export default {
mounted() {
this.resizeFn = ()=>{
console.log(this.$el.clientWidth)
}
window.addEventListner('resize', this.resizeFn)
},
beforeDestroy(){
window.removeEventListener('resize', this.resizeFn)
}
}

But in functional programming like React Hooks, we don’t have this and how can we achieve the same thing?

Let’s say we have a demo which show an input. Upon rendering, user has 30 seconds countdown before input changes to disabled status. Any typing will resolve the countdown.

For this, you might instinctively say, let’s just name a variable inside function body and see how it goes:

export default function App() {
let [text, setText] = useState('')
let [disabled, setDisabled] = useState(false)
let timeout;
console.log(timeout)
let onChange = function (event) {
clearTimeout(timeout)
setText(event.target.value)
}
if (timeout) {
timeout = setTimeout(()=>{
console.log('time is over')
setDisabled(true);
}, 30 * 1000)
}
return (
<div className="App">
<h1>{text}</h1>
<input onChange={onChange} disabled={disabled} value={text}/>
</div>
);
}

It looks good but actually it won’t work. The problem is that when user type anything, the entire function will re-run and timeout will be a brand new variable again with initial value of undefined. So this line will never run:

if (timeout) { doSomething() }

Here comes a special hook called useRef. In the very first part of this series we have already introduced this hook for accessing DOM element inside component. But this can also be used for closure purpose. Here is the explanation from official doc:

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

Here is the correct demo which utilize useRef for store the timeout variable

In both VueJS & React world, declarative programming is preferable way for managing parent-child communication and interaction against imperative programming.

Image we have a form, with an input and a submit button. Button is a child component which will be in disabled status unless input is filled in with an email address. So in declarative way, we will pass a prop to button like:


<form>
<input type="email" />
<Button disabled="disabled">Submit</Button>
</form>

In the form component, it does not care how children are implementing disable/enable. It only declares the status that button component should be in.

On contrary to this approach, comes imperative programming, which will specifically calling children’s API being exposed to its parent:


if (isEmailValid) {
button.setEnable(true)
} else {
button.setEnable(false)
}

Above being said, both framework allows their users to calling sub components in imperative way. One reason is that some 3rd party libraries will expose APIs which components need to call imperatively.

VueJS


<Parent>
<Child ref="button" />
</Parent>

It provides $refs to both DOM and child components so that parent can access directly by call:


this.$refs.button.setEnable()

It even provides two special internal properties $parent and $children to refer to the parent and children components respectively. However this needs to be used with precautions as VueJS does not guarantee rendering order in children so if you write some code like


this.$children[0].setEnable

It will be likely to fail sometime later and it is difficult to debug.

React Hooks

For React Hooks, it needs to use a built-in hooks called useImperativeHandle:


const Button = forwardRef((props, ref) => {
let [disabled, setDisabled] = useState(true)
useImperativeHandle(ref, () => ({
setEnable(flag) {
setDisabled(!flag)
}
}));
return <button disabled={disabled}>Hi</h1>;
});
const Parent = () => {
const buttonRef = useRef();
return (
<form>
<input type="email" onChange={buttonRef.current.setEnable()}/>
<Button ref={buttonRef} />
</form>
);
};

In combination with useRef API we introduced before (which can also be referred to both component and DOM element), we can also call child component methods in imperative way

At first glance, it might look like React is not intuitive against VueJS in this specific design. But React can designate which APIs to be exposed to parent instead of exposing entire child component instance. So to use imperative programming in VueJS way, it’s up to developer’s responsibility to make sure it is safe, reasonable and will not mess up child component internal state and model.

What is Computation?

Computation is a technique we often used in programming to save the computed results of expensive function call, which usually depends on input values. Unless those input values changed, the computed result will stay in the memory unchanged, thus optimizing performance by saving from unless re-computation and re-rendering of UI. That’s why it is called as Memorization.

A very basic example is to suppose we have a user list and search box. We can add some computation field called displayUsers to calculate based on the user list, which usually hold the complete data you pull from data backend, against whatever keyword use types in search box.

VueJS: Computed Property

VueJS offer computed property as a part of its class structure.

Remember, the input value in computed property should be reactive so that VueJS can automatically add watcher and fire the computing functions as soon as any of the dependences change.

React Hooks: useMemo

React offer useMemo hooks to do such computation

Comparison

VueJS computed property is more intuitive: value B is based on value A with some functions. But it is inexplicit in its dependence. You have to look at the call for this.xxxx within function body.

On the other hand, React requires user to declare dependence in their useMemo hook. And since you have to pass state or prop into the dependence, it is unlikely to meet recursive problem.

Both VueJS and ReactJS recommend developers to use their framework imperatively, which means developers ought to take care of business logic purely and let the framework do the dirty work: updating, removing and inserting DOM element based on data in View Model side, hence the MVVM patterns.

However, there are certain scenarios where the developers must get his hands dirty and manipulate the DOM, like playing an audio/video element, managing focus and scrolling a long list etc. The tricky thing is that both frameworks are actually updating UI asynchronously, due to performance concerns and its internal mechanism for dependency collection. So in below code:

Read more »