Open gnosis23 opened 2 years ago
介绍了:
useSubscription
hookconst createStore = (initialState) => {
let state = initialState;
const callbacks = new Set();
const getState = () => state;
const setState = (nextState) => {
state =
typeof nextState === 'function'
? nextState(state)
: nextState;
callbacks.forEach((callback) => callback());
};
const subscribe = (callback) => {
callbacks.add(callback);
return () => {
callbacks.delete(callback);
};
}
return { getState, setState, subscribe }
};
const store = createStore({ count1: 0, count2: 0 });
const Component1 = () => {
const state = useSubscription(useMemo(() => ({
getCurrentValue: () => store.getState().count1,
subscribe: store.subscribe,
}), []));
const inc = () => {
store.setState(prev => ({ ...prev, count1: prev.count1 + 1 }))
};
return (
<div>
{state} <button onClick={inc}>+1</button>
</div>
)
};
第四章的订阅模式有个问题:它的状态是唯一的,如果自组件要自己的状态,就会有问题。 这章介绍了利用 Context 来创建独立的空间
const StoreContext = createContext(createStore({ count: 0, text: 'hello' }));
const StoreProvider = ({ initialState, children }) => {
const storeRef = useRef();
if (!storeRef.current) {
storeRef.current = createStore(initialState);
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
);
};
const useSelector = (selector) => {
const store = useContext(StoreContext);
return useSubscription(
useMemo(
() => ({
getCurrentValue: () => selector(store.getState()),
subscribe: store.subscribe
}),
[store, selector]
)
)
};
const useSetState = () => {
const store = useContext(StoreContext);
return store.setState;
};
const selectCount = (state) => state.count;
const Component = () => {
const count = useSelector(selectCount);
const setState = useSetState();
const inc = () => {
setState(prev => ({ ...prev, count: prev.count + 1 }))
};
return (
<div>
{count} <button onClick={inc}>+1</button>
</div>
)
};
介绍了减少重复渲染有那几种方式:
手动指定跟踪哪个属性变化
const Component = () => {
const value = useSelector(state => state.a.b);
return <>{value}</>
}
自动检测需要监控的状态
const Component = () => {
const trackedState = useTrackedState();
return (
<>
<p>{trackedState.b.c}</p>
</>
);
};
没看懂
const globalState = {
a: atom(1),
b: atom(2),
}
const Component = () => {
const value = useAtom(globalState.a);
return <>{value}</>
};
selector 从大的状态中选取部分状态,atom更像是自底向上构建,通过小的 atom 组成大的 atom。 里面的状态总是需要的
比如:
const firstNameAtom = atom('React');
const lastNameAtom = atom('Hooks');
const ageAtom = atom(3);
const personAtom = atom((get) => ({
firstName: get(firstNameAtom),
lastName: get(lastNameAtom),
age: get(ageAtom),
}));
const PersonComponent = () => {
const [person] = useAtom(personAtom);
return <>{person.firstName} {person.lastName}</>
}
valtio用代理跟踪变化,记住:
Create a local snapshot that catches changes. Rule of thumb: read from snapshots, mutate the source.
import { memo, useState } from "react";
import { proxy, useSnapshot } from "valtio";
let nanoid = 0;
const state = proxy({
todos: [],
});
const createTodo = (title) => {
state.todos.push({
id: nanoid++,
title,
done: false,
});
};
const removeTodo = (id) => {
const index = state.todos.findIndex((item) => item.id === id);
state.todos.splice(index, 1);
};
const toggleTodo = (id) => {
const index = state.todos.findIndex((item) => item.id === id);
state.todos[index].done = !state.todos[index].done;
};
const TodoItem = ({ id }) => {
const todoState = state.todos.find((todo) => todo.id === id);
if (!todoState) {
throw new Error("invalid todo id");
}
const { title, done } = useSnapshot(todoState);
return (
<div>
<input type="checkbox" checked={done} onChange={() => toggleTodo(id)} />
<span
style={{
textDecoration: done ? "line-through" : "none",
}}
>
{title}
</span>
<button onClick={() => removeTodo(id)}>Delete</button>
</div>
);
};
const MemoedTodoItem = memo(TodoItem);
const TodoList = () => {
const { todos } = useSnapshot(state);
const todoIds = todos.map((todo) => todo.id);
return (
<div>
{todoIds.map((todoId) => (
<MemoedTodoItem key={todoId} id={todoId} />
))}
</div>
);
};
const NewTodo = () => {
const [text, setText] = useState("");
const onClick = () => {
createTodo(text);
setText("");
};
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={onClick} disabled={!text}>
Add
</button>
</div>
);
};
这个 useRef 和 useEffect 来统计渲染次数
const Counter1 = () => {
const { count1 } = useContext(CountContext);
const renderCount = useRef(1);
useEffect(() => {
renderCount.current += 1;
});
return (
<div>Count1: {count1}</div>
);
};
前三章
上推组件(lift up)避免重复渲染
设计模式:把组件往上层推,来避免因组件渲染而导致所有组件渲染。
例一
下面这个例子,虽然 Parent 里面的 count 增加了,但是 inner 是外面传递进来的,不会重复渲染 (这个例子用了 inner 属性,其实 children 也可以)
例二
下面例子里有个多 Provider ,也用了这个技巧。上层 Provider 渲染不会导致 下层 Provider 渲染
Context 如何传播
Context for Objects
当使用 object 来作为 context 的值时,会碰到不必要渲染的问题,比如:当点击任意按钮的时候,明明组件 ComponentCount1 只依赖的 count1,但是点击按钮2 的时候它也会重新渲染。
React.memo
使用 memo 来减少父组件渲染导致的重复渲染
将 Context 值拆成多个原始值
当 Context 的 value 为对象,那么每次对象更新,所有 Consumer 都会更新,但并不是所有子组件都需要所有状态值的。 这会造成额外的渲染,可以看这个例子
所以作者推荐将状态拆成几个独立的 Context(如果用 Dispatch 也可以独立做成一个)
使用工厂模式创建Context
作者提炼了一个创建Context的工厂方法,使用它可以方便的创建。 这个方法还支持自定义状态的hook,比如可以用 useState 或者 useReducer。还支持自定义初始值
Context不是设计用来管理状态的