学习《SolidJS》(七)响应式03

150 阅读3分钟

Context

如果是使用过React的肯定已经理解,Context是可以跨越子孙组件传递数据。至于具体的情况下有很多,主题切换,多语言支持,表单类组件(如RadioGroup和Radio的关系)。不要为了传值方便而使用Context,不然容易造成严重的耦合。用法就以RadioGroup进行举例:

import {createSignal, createContext, useContext, JSX, Accessor} from "solid-js";

const RadioContext = createContext<{ value: Accessor<any>, onChange(value: any): void }>();

function RadioGroup(props: { value: any, children: JSX.Element, onChange?(value: any): void }) {
  const value = () => props.value

  const onChange = (value: any) => {
    props.onChange?.(value)
  }

  return (
    <RadioContext.Provider value={{value, onChange}}>
      {props.children}
    </RadioContext.Provider>
  )
}

function useRadioGroupContext() {
  return useContext(RadioContext)
}

function Radio(props: { value?: any, selected?: boolean, onChange?(selected: boolean): void }) {
  const context = useRadioGroupContext()

  const checked = () =>
    context?.value ? props.value === context.value() : props.selected


  const handleChange = () => {
    if (context?.onChange) {
      context.onChange(props.value)
    } else {
      props.onChange?.(!props.selected)
    }
  }

  return (
    <input type="radio" checked={checked()}
           onClick={handleChange}/>
  )
}


function App() {
  const [selected, setSelected] = createSignal(false)
  const [value, setValue] = createSignal(1)

  return (
    <div>
      <div>
        <RadioGroup value={value()} onChange={value => setValue(value)}>
          <div>---分组---</div>
          <div style={{display: "flex", "flex-direction": "column"}}>
            <div>
              <Radio value={1}/>
              <span>1.java</span>
            </div>
            <div>
              <Radio value={2}/>
              <span>2.javascript</span>
            </div>
            <div>
              <Radio value={3}/>
              <span>3.typescript</span>
            </div>
          </div>
        </RadioGroup>
      </div>
      <div>
        <div>---单独使用---</div>
        <Radio selected={selected()} onChange={selected => setSelected(selected)}/>
        <span>启用</span>
      </div>
    </div>
  )
}

用法其实与React基本类似,不一样的点在于,context的value中数据要使用响应式的数据。这个例子其实就很好的说明了,使用context的场景。

组件之间要有明确的数据传递

组件之间不一定是父子关系

数据总控制在组件树的一个共同父节点上

批量更新

SolidJS的响应式数据,与Effect之间是直接调用的。也就是当我们直接调用Setter的时候,就会立即调用所有的Effect函数。

那就会有个问题,导致如果调用多个不同的Setter,然后界面上又同时使用了(如衍生Signal中),这几个Setter对应的数据,就会导致界面多次更新。

不过这其实一般都没有问题,只是有些时候我们还是希望好几个Setter都设置之后再进行界面更新

那就要用到batch函数

const updateNames = () => { 
    console.log("Button Clicked"); 
    batch(() => { 
        setFirstName(firstName() + "n"); 
        setLastName(lastName() + "!"); 
    }) 
 }

这里就直接引用官方的例子,batch的参数接收的是一个函数,函数里面就包含了所有的setter。当所有的setter执行完之后才会执行它们所对应的Effect函数。

untrack

const [a, setA] = createSignal(1);
const [b, setB] = createSignal(1);
createEffect(() => { console.log(a(), untrack(b)); });

untrack这函数就是顾名思义,不跟踪。untrack需要传入的是函数,并且可以简单的理解为立即执行(其实要先执行一些准备工作),所以会在控制台同时打印出1,1。因为不跟踪,所以只有在a改变的时候才会触发Effect,b不管更改多少次都不会执行Effect。 虽然大家肯定都懂,在Effect执行的时候获取的b必定是最新值。

on

与untrack刚好相反,是当指定的响应式数据变化的时候才执行

  const [a, setA] = createSignal(1);
  const [b, setB] = createSignal(1);
  const [c, setC] = createSignal(1);

  createEffect(on([a, c], (data) => {
    console.log(data, b());
  }, {defer: true}));

上面这样写就是指定当a或c变化的时候才执行。其中data就是当前a、c的值,是一个数组形式就是on的第一个参数[a, c]这样这个样子。另外还可以看到,有个defer参数,这个参数的意思就是不立即执行(createEffect是立即执行的),当a或c出现改变的时候才执行。