|
3 | 3 | > 转载自掘金文章:[React17 源码分析](https://juejin.cn/post/6898635086657224717) |
4 | 4 | > author: [xfz](https://juejin.cn/user/1415826705485128) |
5 | 5 |
|
| 6 | +## 19 版本特点 |
| 7 | + |
| 8 | +### Actions |
| 9 | + |
| 10 | +- useTransition |
| 11 | +- useActionState |
| 12 | +- useFormState |
| 13 | +- useOptimistic |
| 14 | +- use |
| 15 | + |
| 16 | +> React 19 新内容:[React19 new content](https://react.dev/blog/2024/12/05/react-19) |
| 17 | +
|
| 18 | +> 以下所有案例,直接复制到 codesandbox 中运行即可,缺少的 import 请自行补充。 |
| 19 | +
|
| 20 | +- Actions: useTransition 任务执行方法及状态 |
| 21 | +```javascript |
| 22 | +const updateName = () => { |
| 23 | + return new Promise((resolve, reject) => { |
| 24 | + setTimeout(() => { |
| 25 | + Math.random() > 0.5 |
| 26 | + ? resolve("finished") |
| 27 | + : reject(new Error("error get")); |
| 28 | + }, 1000); |
| 29 | + }); |
| 30 | +}; |
| 31 | + |
| 32 | +export default function App() { |
| 33 | + const [name, setName] = useState("init"); |
| 34 | + const [error, setError] = useState(null); |
| 35 | + const [isPending, startTransition] = useTransition(); |
| 36 | + |
| 37 | + const handleSubmit = () => { |
| 38 | + startTransition(async () => { |
| 39 | + const res = await updateName().catch((err) => setError(err)); |
| 40 | + res && setName(res); |
| 41 | + }); |
| 42 | + }; |
| 43 | + |
| 44 | + return ( |
| 45 | + <div className="App"> |
| 46 | + <h1>Hello {name}</h1> |
| 47 | + <h2>Start editing to see some magic happen!</h2> |
| 48 | + <button onClick={handleSubmit} disabled={isPending}> |
| 49 | + Update |
| 50 | + </button> |
| 51 | + {error && <p>{error.toString()}</p>} |
| 52 | + </div> |
| 53 | + ); |
| 54 | +} |
| 55 | +``` |
| 56 | +- Actions: useActionState |
| 57 | +```javascript |
| 58 | + |
| 59 | +const updateName = (name) => { |
| 60 | + return new Promise((resolve, reject) => { |
| 61 | + setTimeout(() => { |
| 62 | + Math.random() > 0.5 |
| 63 | + ? resolve(`${name} finished`) |
| 64 | + : reject(new Error("error get")); |
| 65 | + }, 1000); |
| 66 | + }); |
| 67 | +}; |
| 68 | + |
| 69 | +export default function App() { |
| 70 | + const [error, setError] = useState(null); |
| 71 | + const [state, submitAction, isPending] = useActionState( |
| 72 | + async (previousState, formData) => { |
| 73 | + setError(null); |
| 74 | + const state = await updateName(formData.get("name")).catch((err) => |
| 75 | + setError(err) |
| 76 | + ); |
| 77 | + if (state) { |
| 78 | + return state; |
| 79 | + } |
| 80 | + }, |
| 81 | + null |
| 82 | + ); |
| 83 | + |
| 84 | + return ( |
| 85 | + <form action={submitAction}> |
| 86 | + <input name="name" /> |
| 87 | + <button type="submit" disabled={isPending}> |
| 88 | + Update |
| 89 | + </button> |
| 90 | + <p>state: {state}</p> |
| 91 | + {error && <p>{error.toString()}</p>} |
| 92 | + </form> |
| 93 | + ); |
| 94 | +} |
| 95 | +``` |
| 96 | +- Actions: useFormState |
| 97 | +```javascript |
| 98 | +function SubmitButton() { |
| 99 | + const { pending } = useFormStatus(); |
| 100 | + |
| 101 | + return ( |
| 102 | + <div className="form_item"> |
| 103 | + <button className="primary" type="submit" disabled={pending}> |
| 104 | + {pending ? "Submitting..." : "Submit"} |
| 105 | + </button> |
| 106 | + </div> |
| 107 | + ); |
| 108 | +} |
| 109 | + |
| 110 | +export default function App() { |
| 111 | + const [state, submitAction, isPending] = useActionState( |
| 112 | + async (previousState, formData) => { |
| 113 | + const title = formData.get("name"); |
| 114 | + |
| 115 | + await new Promise((resolve) => setTimeout(resolve, 1000)); |
| 116 | + return [...(previousState || []), title]; |
| 117 | + }, |
| 118 | + null |
| 119 | + ); |
| 120 | + |
| 121 | + return ( |
| 122 | + <form action={submitAction}> |
| 123 | + <input type="text" name="name" /> |
| 124 | + <p>posts: {isPending ? "loading" : (state || []).join(",")}</p> |
| 125 | + <SubmitButton /> |
| 126 | + </form> |
| 127 | + ); |
| 128 | +} |
| 129 | +``` |
| 130 | +- Actions: useOptimistic |
| 131 | +```javascript |
| 132 | +const updateName = (name) => { |
| 133 | + return new Promise((resolve, reject) => { |
| 134 | + setTimeout(() => { |
| 135 | + resolve(`${name} finished`); |
| 136 | + }, 1000); |
| 137 | + }); |
| 138 | +}; |
| 139 | +function SubmitButton() { |
| 140 | + const { pending } = useFormStatus(); |
| 141 | + |
| 142 | + return ( |
| 143 | + <div className="form_item"> |
| 144 | + <button className="primary" type="submit" disabled={pending}> |
| 145 | + {pending ? "Submitting..." : "Submit"} |
| 146 | + </button> |
| 147 | + </div> |
| 148 | + ); |
| 149 | +} |
| 150 | + |
| 151 | +function ChangeName({ currentName, onUpdateName }) { |
| 152 | + const [optimisticName, setOptimisticName] = useOptimistic(currentName); |
| 153 | + |
| 154 | + const submitAction = async (formData) => { |
| 155 | + const newName = formData.get("name"); |
| 156 | + setOptimisticName(newName); |
| 157 | + const updatedName = await updateName(newName); |
| 158 | + onUpdateName(updatedName); |
| 159 | + }; |
| 160 | + |
| 161 | + return ( |
| 162 | + <form action={submitAction}> |
| 163 | + <p>Your name is: {optimisticName}</p> |
| 164 | + <p> |
| 165 | + <label>Change Name:</label> |
| 166 | + <input |
| 167 | + type="text" |
| 168 | + name="name" |
| 169 | + disabled={currentName !== optimisticName} |
| 170 | + /> |
| 171 | + </p> |
| 172 | + <SubmitButton /> |
| 173 | + </form> |
| 174 | + ); |
| 175 | +} |
| 176 | + |
| 177 | +export default function App() { |
| 178 | + const [currentName, updateName] = useState(""); |
| 179 | + |
| 180 | + return <ChangeName currentName={currentName} onUpdateName={updateName} />; |
| 181 | +} |
| 182 | + |
| 183 | +``` |
| 184 | +- Actions:use |
| 185 | + |
| 186 | +使用 Promise 及 Context 示例 |
| 187 | + |
| 188 | +```javascript |
| 189 | +function Comments({ commentsPromise }) { |
| 190 | + // `use` will suspend until the promise resolves. |
| 191 | + const comments = use(commentsPromise); |
| 192 | + return comments.map((comment) => <p key={comment.id}>{comment.comment}</p>); |
| 193 | +} |
| 194 | + |
| 195 | +function Page({ commentsPromise }) { |
| 196 | + // When `use` suspends in Comments, |
| 197 | + // this Suspense boundary will be shown. |
| 198 | + return ( |
| 199 | + <Suspense fallback={<div>Loading...</div>}> |
| 200 | + <Comments commentsPromise={commentsPromise} /> |
| 201 | + </Suspense> |
| 202 | + ); |
| 203 | +} |
| 204 | + |
| 205 | +const themeContext = createContext({ color: "light" }); |
| 206 | + |
| 207 | +export default function App() { |
| 208 | + const theme = use(themeContext); |
| 209 | + |
| 210 | + console.log("theme color:", theme?.color); |
| 211 | + |
| 212 | + const commentsPromise = new Promise((resolve) => { |
| 213 | + setTimeout(() => { |
| 214 | + resolve([ |
| 215 | + { id: 1, comment: "hello 1" }, |
| 216 | + { id: 2, comment: "hello 2" }, |
| 217 | + { id: 3, comment: "hello 3" }, |
| 218 | + { id: 4, comment: "hello 4" }, |
| 219 | + ]); |
| 220 | + }, 1000); |
| 221 | + }); |
| 222 | + |
| 223 | + return <Page commentsPromise={commentsPromise} />; |
| 224 | +} |
| 225 | +``` |
| 226 | +
|
| 227 | +### Improvements |
| 228 | +
|
| 229 | +- ref as a prop |
| 230 | +
|
| 231 | +> 移除了 forwardRef 的写法 |
| 232 | +
|
| 233 | +```javascript |
| 234 | +function MyInput({placeholder, ref}) { |
| 235 | + return <input placeholder={placeholder} ref={ref} /> |
| 236 | +} |
| 237 | + |
| 238 | +//... |
| 239 | +<MyInput ref={ref} /> |
| 240 | +``` |
| 241 | +
|
| 242 | +- \<Context\> as a provider |
| 243 | +
|
| 244 | +> Context 替代了 <del>Context.Provider</del>「已弃用」 |
| 245 | +
|
| 246 | +```javascript |
| 247 | +const ThemeContext = createContext(''); |
| 248 | + |
| 249 | +function App({children}) { |
| 250 | + return ( |
| 251 | + <ThemeContext value="dark"> |
| 252 | + {children} |
| 253 | + </ThemeContext> |
| 254 | + ); |
| 255 | +} |
| 256 | +``` |
| 257 | +
|
| 258 | +- Cleanup functions for refs |
| 259 | +
|
| 260 | +```javascript |
| 261 | +<input |
| 262 | + ref={(ref) => { |
| 263 | + // ref created |
| 264 | + |
| 265 | + // NEW: return a cleanup function to reset |
| 266 | + // the ref when element is removed from DOM. |
| 267 | + return () => { |
| 268 | + // ref cleanup |
| 269 | + }; |
| 270 | + }} |
| 271 | +/> |
| 272 | + |
| 273 | +// 停止使用 隐式返回,因为引入了 ref 清理函数,需改为下面写法 |
| 274 | +- <div ref={current => (instance = current)} /> |
| 275 | ++ <div ref={current => {instance = current}} /> |
| 276 | +``` |
| 277 | +
|
| 278 | +- useDeferredValue initial value |
| 279 | +
|
| 280 | +当提供 initialValue 时,useDeferredValue 会将其作为组件初始渲染的值返回,并使用返回的 deferredValue 在后台安排重新渲染。 |
| 281 | +```javascript |
| 282 | +function Search({deferredValue}) { |
| 283 | + // 首次渲染的值为 空字符串 |
| 284 | + // 然后使用新得到的 deferredValue 安排重新渲染 |
| 285 | + const value = useDeferredValue(deferredValue, ''); |
| 286 | + |
| 287 | + return ( |
| 288 | + <Results query={value} /> |
| 289 | + ); |
| 290 | +} |
| 291 | +``` |
| 292 | +
|
6 | 293 | ## 15 版本特点 |
7 | 294 |
|
8 | 295 | React 15 的架构分为两层 |
|
0 commit comments