学习《SolidJS》(六)响应式02

内嵌响应式

在React里面,如果有一个数组,并且数组内的元素是对象。然后需要仅仅修改其中一个属性,那就是会解构数组,解构对象,然后进行更改。用SolidJS写就比如下面这样

tsx 复制代码
import {createSignal, For} from "solid-js";

interface Todo{
  id: number,
  name: string,
  completed: boolean,
}

const App = () => {
  let todoId = 0;
  const [todos, setTodos] = createSignal<Todo[]>([]);
  const [inputValue, setInputValue] = createSignal('');
  const addTodo = () => {
    if(inputValue().trim() === ""){
      return
    }

    setTodos(pre => [...pre, {id: ++todoId, name: inputValue(), completed: false}])
  }

  const toggleTodo = (id: number) => {
    setTodos(pre => pre.map(todo => {
      return todo.id !== id ? todo: {...todo, completed: !todo.completed}
    }))
  }

  return (
    <div style={{display: "flex", "flex-direction": "column"}}>
      <div style={{display: "flex"}}>
        <input type="text" value={inputValue()} onInput={e => setInputValue(e.target.value)}/>
        <button onClick={addTodo}>添加</button>
      </div>
      <For each={todos()}>
        {
          todo => {
            console.log(`Creating ${todo.name}`)
            return (
              <div style={{display: "flex"}}>
                <input type="checkbox" checked={todo.completed} onchange={() => toggleTodo(todo.id)}/>
                <div>{todo.id}.{todo.name}</div>
              </div>
            )
          }
        }
      </For>
    </div>
  )
};

当我们点击checkbox的时候:

如果在React里面这样写是没有问题的,因为React会对比fiber node找到不一样的地方再进行更新,所以更新的时候只会更新input。
但是在SolidJS中没有做这样的对比,todo.completed又不是响应式数据没办法使用effect监听变化然后更新(虽然编译后有使用effect函数包裹),只能重新执行创建For中的函数,也就是重新创建整行todo。所以每次点击checkbox都能在控制台看到Creating xxx。

这种情况我们可以使用内嵌的响应式数据,写法如下

tsx 复制代码
import {Accessor, createSignal, For, Setter} from "solid-js";

interface Todo{
  id: number,
  name: string,
  completed: Accessor<boolean>,
  setCompleted: Setter<boolean>
}

const App = () => {
  let todoId = 0;
  const [todos, setTodos] = createSignal<Todo[]>([]);
  const [inputValue, setInputValue] = createSignal('');
  const addTodo = () => {
    if(inputValue().trim() === ""){
      return
    }
    const [completed, setCompleted] = createSignal(false)
    setTodos(pre => [...pre, {id: ++todoId, name: inputValue(), completed, setCompleted}])
  }

  const toggleTodo = (id: number) => {
    todos().find(todo => todo.id === id)?.setCompleted(pre => !pre)
  }

  return (
    <div style={{display: "flex", "flex-direction": "column"}}>
      <div style={{display: "flex"}}>
        <input type="text" value={inputValue()} onInput={e => setInputValue(e.target.value)}/>
        <button onClick={addTodo}>添加</button>
      </div>
      <For each={todos()}>
        {
          todo => {
            console.log(`Creating ${todo.name}`)
            return (
              <div style={{display: "flex"}}>
                <input type="checkbox" checked={todo.completed()} onchange={() => toggleTodo(todo.id)}/>
                <div>{todo.id}.{todo.name}</div>
              </div>
            )
          }
        }
      </For>
    </div>
  )
};

也就是说,把completed改为响应式数据,并且在对象里面添加对应的Setter,在需要的时候取出来更新。改成这样其实就是让编译器在checked={todo.completed()}这个地方改成了createEffect。

不过说实话,我不太喜欢这样用,也没啥原因,就是太麻烦了。

所以这里其实也暴露出了createSignal的缺点,与Vue相比,它响应式只有浅层的。

Store

使用内嵌响应式除了麻烦还有一个很大的问题,那就是修改了对象,还加了额外的属性setCompleted,这是真的不好。

所以SolidJS提供了深层的响应式createStore,修改上面的代码只会就得到

tsx 复制代码
import {createSignal, For} from "solid-js";
import {createStore} from "solid-js/store";

interface Todo {
  id: number,
  name: string,
  completed: boolean
}

const App = () => {
  let todoId = 0;
  const [todos, setTodos] = createStore<Todo[]>([]);
  const [inputValue, setInputValue] = createSignal('');
  const addTodo = () => {
    if (inputValue().trim() === "") {
      return
    }
    setTodos(todos => [...todos, {id: ++todoId, name: inputValue(), completed: false}])
  }

  const toggleTodo = (id: number) => {
    setTodos(t => t.id === id, 'completed', completed => !completed)
  }
  
  return (
    <div style={{display: "flex", "flex-direction": "column"}}>
      <div style={{display: "flex"}}>
        <input type="text" value={inputValue()} onInput={e => setInputValue(e.target.value)}/>
        <button onClick={addTodo}>添加</button>
      </div>
      <For each={todos}>
        {
          todo => {
            console.log(`Creating ${todo.name}`)
            return (
              <div style={{display: "flex"}}>
                <input type="checkbox" checked={todo.completed} onchange={() => toggleTodo(todo.id)}/>
                <div>{todo.id}.{todo.name}</div>
              </div>
            )
          }
        }
      </For>
    </div>
  )
};

使用了createStore之后,todos不再是一个函数,而是一个被Proxy代理后的数组,也就是与Vue中类似,我们使用todos[1]的时候就相当于调用了一个getter函数,此时就可以被CreateEffect这样的函数捕获到,todos[1].completed也同样触发了依赖收集。

不过与Vue不同的是Setter是另外的一个函数,如果你直接复制是不会有效果, 还要setTodos才可以触发ui更新

这里需要介绍一下Setter函数, 最简单的用法就Singal是一样的

tsx 复制代码
//直接赋予新值
setTodos([{id: ++todoId, name: 'lisi', completed: false}])
//用函数可以得到当前值
setTodos(todos => [...todos, {id: ++todoId, name: 'lisi', completed: false}])

如果只是想修改数组中对象的某个属性,就要用到路径选择

tsx 复制代码
setTodos(t => t.id === id, 'completed', completed => !completed)

对于数组来说,路径选择就类似于find函数一样。对于对象来说就是传入属性名称。

最后一个函数就是已经锁定了对应的属性,这个时候就是传入该属性需要的值或者是可以返回该属性的值函数

produce

刚刚也说了,不可以像Vue那样直接复制,但是有些时候又需要复杂逻辑的判断才可以修改,如果使用路径选择setter其实也不是那么的方便,又或者写出来了就一大堆的...之类的。SolidJS提供了produce,这是一个受 Immer 启发的函数。有了它之后可以将addTodo和toggleTodo改成下面这样

tsx 复制代码
//...省略
import {createStore, produce} from "solid-js/store";
//...省略
const addTodo = () => {
  if (inputValue().trim() === "") {
    return
  }
  setTodos(produce(todos => todos.push({id: ++todoId, name: inputValue(), completed: false})))
}

const toggleTodo = (id: number) => {
  setTodos(
    produce(todos => {
        let todo = todos.find(t => t.id === id);
        if (todo) {
          todo.completed = !todo.completed
        }
      }
    )
  )
}

上面代码并没有写得很好,因为我只是为了演示可以produce可以随意操作。

不可变Store

在官方文档里面关于它的介绍是用来与其它库进行交互的,我太菜了看不懂(绝对不是不喜欢用),所以我就不做介绍了,直接略过。

相关推荐
秦jh_8 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑21320 分钟前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy21 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪1 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与2 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun2 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇2 小时前
ES6进阶知识一
前端·ecmascript·es6
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法