This is the 7th day of my participation in the August Text Challenge.More challenges in August

preface

In contrast, Vue is trying to keep up with React hooks. Reactive variables make the mental load lighter, and React has far fewer strange screen actions. Is there a way to make React use reactive updates without wondering which useCallback forgot to add variable dependencies? Now, try radioactive-state

use

import useRS from 'radioactive-state';

const state = useRS({
  input: ' '
})

<input
  value={state.input}
  onChange={(e) = > state.input = e.target.value}
  type='text'
/>
Copy the code

As you can see, objects that pass through the useRS package return a state. State lets you get and set the value of a reactive variable. State. input is used in input, and the latest value is assigned to state.input when the onChange event is triggered.

It is extremely simple to use, just like reactive variables in Vue.

Let’s take a look at the magic of useRS through the source code.

useRS

const useRS = arg= > {
    const [, forceUpdate] = useReducer(x= > x + 1.0)
    // ...
}
Copy the code

As you can see, this creates a reducer with the useReducer and the second set function is called forceUpdate. This function is used to force a refresh on React.

Let’s keep watching.

const useRS = arg= > {
    // ...
    const ref = useRef()
    
    if(! ref.current) {const initialState = unwrap(arg)
        checkInitialState(initialState)
        ref.current = reactify(checkInitialState, forceUpdate)
    }
    
    return ref.current
}
Copy the code

A ref is created with useRef and the unwrap, checkInitialState, and reactify methods are executed.

unwrap

const unwrap = (x) = > typeof x === 'function' ? x() : x
Copy the code

If the parameter passed to useRS is a function type, the function is executed before reactive proxy.

checkInitialState

const checkInitialState = initialState= > {
    if(! isObject(initialState)) {throw new Error(`Invalid State "${initialState}" provided to useRS() hook.`)}}Copy the code

This method validates the passed parameter, which must be of type Array or Object.

reactify

const reactify = (state, forceUpdate, path = []) = > {
  const wrapper = Array.isArray(state) ? [] : {}
  Object.keys(state).forEach(key= > {
    let slice = state[key]
    slice = unwrap(slice)
    wrapper[key] = isObject(slice) ? reactify(slice, forceUpdate, [...path, key]) : slice
  })

  // ...
}
Copy the code

The reactify function takes two parameters: an array or object to bind reactive, and a method to force updates.

First, a Wrapper is created as the final value returned after a reactive binding.

It then iterates through the array or object passed in and takes the return value of the function directly, using the previously mentioned unwrap processing.

Finally, if it is an array or object, recursively reactify and save the key of the object for the recursive procedure. For other types, the assignment is straightforward.

const reactify = (state, forceUpdate, path = []) = > {
  // ...
  
  return new Proxy(wrapper, {
    set(target, prop, value){},deleteProperty(target, prop){},get(target, prop){},})}Copy the code

As you can see, this is the focus of reactify, which finally returns the proxy value for the Wrapper.

The following three parts: set, get, deleteProperty to analyze.

proxy.set

set(target, prop, value) {
    constaddingNonReactiveObject = isObject(value) && ! value.__isRadioactive__const $value = addingNonReactiveObject ? reactify(value, forceUpdate, [...path, prop]) : value

    // if new value and old value are not same
    if(target[prop] ! == $value) { schedule(forceUpdate) mutations++ }return Reflect.set(target, prop, $value)
}
Copy the code

When assigning the state returned by useRS:

  • First check whether the new value needs to bereactify, assigns the value after the proxy to$value
  • Then check whether the original value and the new value are the same, if not, passscheduleTo ReactForced to refresh
const schedule = (cb) = > {
  if(! cb.scheduled) { cb.scheduled =true
    setTimeout(() = > {
      cb()
      cb.scheduled = false
    }, 0)}}Copy the code

In this case, schedule is updated in batches through setTimeout. (Ensure that state is changed multiple times in the synchronization task but forceUpdate is executed only once)

  • Finally,mutations++, and through theReflect.setActually modify the new value.

proxy.get

get(target, prop) {
  if (prop === '__target__') return unReactify(target)
  if (prop === '__isRadioactive__') return true
  // mutation count
  if (prop === '$') return mutations
  // inputBinding
  if (prop[0= = ='$') return inputBinding(prop, target, forceUpdate)
  else return Reflect.get(target, prop)
}
Copy the code

When getting useRS returns a property on state,

  • Access is__target__Returns the original value (the value of the non-responsive binding)
  • Access is__isRadioactive__Is returned true
  • Access is$, returns the number of times the variable was modified. (Multiple sets may only fire onceforceUpdate, but$Always increment)
  • Access is$Attributes at the beginning, for example$ageThe return value of inputBinding is returned
const inputBinding = (prop, target, forceUpdate) = > {
  const actualProp = prop.substring(1)
  if (target.hasOwnProperty(actualProp)) {

    let key = 'value'
    const propType = typeof target[actualProp]
    if (propType === 'boolean') {
      key = 'checked'
    }

    const binding =  {
      [key]: target[actualProp],
      onChange: e= > {
        let value = e.target[key]
        if (propType === 'number') value = Number(value)
        Reflect.set(target, actualProp, value)
        forceUpdate()
      }
    }

    return binding
  }
}
Copy the code

Explanation:

  • First of all check$ageremove$Returns whether the property name after exists, if notundefined
  • If the attribute name exists, determine the attribute type:
    • Boolean returns an object containingchecked,onChange
    • Non-boolean, returns an object,value,onChange
  • For the checked fields, is the field of true | false
  • For the value field, the answer is to the field itself
  • For the onChange event, receive{ event: { value: xx } }Used to modify data and trigger forceUpdate
- For other types of values, the true value is returned via 'reflect.get'Copy the code

proxy.deleteProperty

deleteProperty(target, prop) {
  schedule(forceUpdate)
  mutations++
  return Reflect.deleteProperty(target, prop)
}
Copy the code

When an attribute is removed, forceUpdate is also triggered, and mutations add one.

Finally, the attribute is deleted with reflect.deleteProperty.

conclusion

Radioactive – State implements responsive processing of data through Proxy.

In addition, the concept design of inputBinding and $is also very clever. This library is also a good choice if you want to use reactive variables in React.