以结果为导向,写给刚学完前端三剑客和想要了解 React 框架的小伙伴,使得他们能快速上手(省略了历史以及一些不必要的介绍)。
自定义 hook
Hooks: functions starting with
use
— can only be called at the top level of your components or your own Hooks.
虽然 React 自带很多 hook,但肯定不能涵盖我们所有的需求,所以自定义 hook 就很重要了。
我们将 App 组件里【管理 state 并同步到本地存储的功能】抽离出来,创建一个新的自定义 hook 叫做 useSemiPersistentState
,翻译过来就是半持久化,【半】是因为清除浏览器的本地存储就会把应用所有相关数据都删掉。
const useSemiPersistentState = () => { const [value, setValue] = React.useState( localStorage.getItem("search") || "React" ); React.useEffect(() => { localStorage.setItem("value", value); }, [value]); return [value, setValue]; }; const App = () => { ... const [searchTerm, setSearchTerm] = useSemiPersistentState(); ... }; 复制代码
因为我们自定义 hook 的目标是【可复用性】,所以我们要用抽象的值 ”value“,而在 App 组件中调用时可以使用有业务含义的词来命名。
还有一个问题:如果在一个应用中多次调用这个 hook ,由于名字都是 “value”,会造成本地存储的值被覆盖。
可以通过传入一个 key 值解决,同时让它接收一个初始的 state:
const useSemiPersistentState = (key, initialState) => { const [value, setValue] = React.useState( localStorage.getItem(key) || initialState ); React.useEffect(() => { localStorage.setItem(key, value); }, [value, key]); return [value, setValue]; }; const App = () => { ... const [searchTerm, setSearchTerm] = useSemiPersistentState( "search", "React" ); ... }; 复制代码
因为 key 值来自外部,随时可能发生变化,所以要把它放入 useEffect 的依赖数组里,以防副作用使用了过期的 key 值。
这样我们就创建了第一个自定义 hook,它把复杂的实现细节包装起来,从而使组件逻辑更加清晰,并且可以【复用】到更多的 React 组件中,甚至可以作为第三方库开源出去,比如 streamich/react-use,alibaba/hooks,vercel/swr 等等。
可复用组件
Computer scientists use abstraction to make models that can be used and re-used without having to re-write all the program code for each new application on every different type of computer.
如果我们同时渲染两个 Search 组件,相同的 htmlFor 和 id 属性值一定会引入 bug,并且由于和搜索功能强相关,使得组件的可复用性非常差。
但该组件实际并不具备任何的 “搜索” 能力,我们可以通过传递 id 和 label 等参数将 Search 组件抽象成一个能覆盖其他搜索场景的通用组件:
const App = () => { .... return ( <> <InputWithLabel id="search" label="Search" value={searchTerm} onInputChange={handleSearch} /> <hr /> <List list={searchedStories} /> </> ); }; const InputWithLabel = ({ id, label, value, type = "text", onInputChange }) => ( <> <label htmlFor={id}>{label}</label> <input id={id} type={type} value={value} onChange={onInputChange} /> </> ); 复制代码
在 InputWithLabel 组件中我们指定了 type 的默认参数,在没有值或 undefined
被传入时会使用默认参数