https://github.com/CroMarmot/ReactDemo/commits/master
Next 工具
Vercel 部署 https://vercel.com/new
Init https://nextjs.org/learn/foundations/
1 npx create-next-app@latest --ts
React React use Babel to compile JSX
严格模式 https://zh-hans.reactjs.org/docs/strict-mode.html
识别不安全的生命周期
关于使用过时字符串 ref API 的警告
关于使用废弃的 findDOMNode 方法的警告
检测意外的副作用
检测过时的 context API
确保可复用的状态
1 2 3 4 5 6 7 8 9 10 import React , { StrictMode } from 'react' ;import ReactDOM from 'react-dom' ;ReactDOM .render ( <StrictMode > <App /> </StrictMode > , document .getElementById ('root' ) );
状态 Class组件 1 2 3 4 5 class ExampleClassComponent extends React.Component { render ( ) { return <h1 > Example Class Component</h1 > ; } }
函数式组件 React 组件名必须要首字母大写
1 2 3 function ExampleFunctionalComponent ( ) { return <h1 > Example Class Component</h1 > ; }
1 2 3 const ExampleFunctionalComponent = ( ) => { return <h1 > Example Class Component</h1 > ; }
1 2 3 4 5 6 7 import { memo } from 'React' ;const ExamplePureComponent = memo ({ portal }) => { return ( <h1 > Welcome to {portal}!</h1 > ); }
传参 1 2 3 4 5 6 7 class PropsExample extends React.Component { render ( ) { const { portalName } = this .props ; return <h1 > Welcome to {portalName}</h1 > ; } }
1 2 3 const PropsExample = (props ) => { return <h1 > Welcome to {props.portalName}</h1 > ; }
1 2 3 const PropsExample = ({ portalName } ) => { return <h1 > Welcome to {portalName}</h1 > ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import PropTypes from 'prop-types' ;const App = ({ name, age, rightHanded } ) => { return ( <div > <p > Name: {name.firstName}</p > <p > Surname: {name.lastName}</p > <p > Age: {age}</p > <p > Dominant hand: {rightHanded ? 'right' : 'left'}</p > </div > ); }; App .propTypes = { name : PropTypes .shape ({ firstName : PropTypes .string , lastName : PropTypes .string }), age : PropTypes .number , rightHanded : PropTypes .oneOf ([true , false ]), }; export default App ;
子组件 slot 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const App = () => { return ( <> <ParentComponent> <ul> <li>This</li> <li>is</li> <li>children</li> <li>element</li> </ul> </ParentComponent> </> ); }; const ParentComponent = ({ children }) => { return ( <div> {children} </div> ); };
状态 useState
1 2 3 4 5 6 7 8 9 10 11 12 13 import { useState } from 'react' ;import Counter from './User' ;function App ( ) { const [year, setYear] = useState (1237 ) return ( <div > <Counter warning ={ `Current year is ${year }`} /> </div > ); };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class UserAccount extends React.Component { constructor(props) { super(props); this.state = { username: 'mike123', userAge: 21 }; } render() { return ( <div> <h2>User account</h2> <ul> <li>Username: {this.state.username}</li> <li>Age: {this.state.userAge}</li> </ul> </div> ); } }
跨层级变量 useContext 提供
1 2 3 4 5 6 7 8 9 10 import React , { createContext } from 'react' ;... export const <context-name> = createContext ();ReactDOM .render ( <<context-name>.Provider value={<context-value>}> <App /> </<context-name>.Provider >, document .getElementById ('root' ) );
使用
1 2 3 4 5 6 7 import { useContext } from 'react' ;import { <context-name> } from './index' ;... const <context-variable> = useContext (<context-name>);console .log (<context-variable>);
useEffect
请记住,Effects通常用于 “走出 “你的React代码并与一些外部系统同步。这包括浏览器API、第三方小工具、网络等等。如果你的Effect只是根据其他状态来调整一些状态, 你并不需要useEffect (https://beta.reactjs.org/learn/you-might-not-need-an-effect )
https://www.reddit.com/r/reactjs/comments/v5ypd9/goodbye_useeffect_reactathon_2022/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import axios from 'axios' ;import { useEffect } from 'react' ;const App = ( ) => { useEffect (() => { const fetchData = async ( ) => { ... }; fetchData () }, []); return <div > </div > ; };
We’re calling the fetchData function that’s responsible for fetching data from the server. We execute it on every re-render because the dependency array is empty.
DOM对象 useRef 非常量不需要渲染的引用, 或者对一个对象实体
a React Hook that lets you reference a value that’s not needed for rendering.
和 useState 区别
返回的是 {current}
变化时不触发re-render
在rendering执行外可以 修改, state 是不可修改的
rendering时 不应该读 current(所以你不应该在渲染中用{ref.current}), state 随时有snapshot可读
使用场景
存 timeout id
操作dom元素
存其它对jsx 不必要的objects
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import { useState, useRef } from 'react' ;export default function Stopwatch ( ) { const [startTime, setStartTime] = useState (null ); const [now, setNow] = useState (null ); const intervalRef = useRef (null ); function handleStart ( ) { setStartTime (Date .now ()); setNow (Date .now ()); clearInterval (intervalRef.current ); intervalRef.current = setInterval (() => { setNow (Date .now ()); }, 10 ); } function handleStop ( ) { clearInterval (intervalRef.current ); } let secondsPassed = 0 ; if (startTime != null && now != null ) { secondsPassed = (now - startTime) / 1000 ; } return ( <> <h1 > Time passed: {secondsPassed.toFixed(3)}</h1 > <button onClick ={handleStart} > Start </button > <button onClick ={handleStop} > Stop </button > </> ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { useState, useRef, useEffect } from 'react' ;function VideoPlayer ({ src, isPlaying } ) { const ref = useRef (null ); useEffect (() => { if (isPlaying) { ref.current .play (); } else { ref.current .pause (); } }); return <video ref ={ref} src ={src} loop playsInline /> ; }
自定义 state 的处理逻辑 useReducer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import { useReducer } from "react" ;import ReactDOM from "react-dom/client" ;const initialTodos = [ { id : 1 , title : "Todo 1" , complete : false , }, { id : 2 , title : "Todo 2" , complete : false , }, ]; const reducer = (state, action ) => { switch (action.type ) { case "COMPLETE" : return state.map ((todo ) => { if (todo.id === action.id ) { return { ...todo, complete : !todo.complete }; } else { return todo; } }); default : return state; } }; function Todos ( ) { const [todos, dispatch] = useReducer (reducer, initialTodos); const handleComplete = (todo ) => { dispatch ({ type : "COMPLETE" , id : todo.id }); }; return ( <> {todos.map((todo) => ( <div key ={todo.id} > <label > <input type ="checkbox" checked ={todo.complete} onChange ={() => handleComplete(todo)} /> {todo.title} </label > </div > ))} </> ); } const root = ReactDOM .createRoot (document .getElementById ('root' ));root.render (<Todos /> );
复用 useMemo , useCallback 包裹自己的话, 用React.memo的高阶函数包裹
例如 页面有输入框, 也有和输入框无关的 函数计算, 可以让输入框变化时, 那些无关的计算不会被调用
1 2 3 4 5 6 7 8 9 10 11 import { useState, useMemo } from "react" ;const calculation = useMemo (() => expensiveCalculation (count), [count]);const expensiveCalculation = (num ) => { console .log ("Calculating..." ); for (let i = 0 ; i < 1000000000 ; i++) { num += 1 ; } return num; };
这种情况 count是 数字不会影响
而如果 props中有传回调函数, 而组件自身会变化, 那么回调函数地址会变化, useMemo无法做到希望的”入参不变不重渲染”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import { useState, useCallback } from "react" ;import ReactDOM from "react-dom/client" ;import Todos from "./Todos" ;const App = ( ) => { const [count, setCount] = useState (0 ); const [todos, setTodos] = useState ([]); const increment = ( ) => { setCount ((c ) => c + 1 ); }; const addTodo = useCallback (() => { setTodos ((t ) => [...t, "New Todo" ]); }, [todos]); return ( <> <Todos todos ={todos} addTodo ={addTodo} /> <hr /> <div > Count: {count} <button onClick ={increment} > +</button > </div > </> ); }; const root = ReactDOM .createRoot (document .getElementById ('root' ));root.render (<App /> );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { memo } from "react" ;const Todos = ({ todos, addTodo } ) => { console .log ("child render" ); return ( <> <h2 > My Todos</h2 > {todos.map((todo, index) => { return <p key ={index} > {todo}</p > ; })} <button onClick ={addTodo} > Add Todo</button > </> ); }; export default memo (Todos );
Redux 1 yarn add @reduxjs/toolkit react-redux
Store
1 2 3 4 5 6 7 8 import { configureStore } from "@reduxjs/toolkit"; import counterReducer from "./features/counter/counterSlice"; export default configureStore({ reducer: { counter: counterReducer, }, });
Slice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import { createSlice } from "@reduxjs/toolkit"; export const counterSlice = createSlice({ name: "counter", initialState: { value: 0, }, reducers: { increment: (state) => { // Redux Toolkit allows us to write "mutating" logic in reducers. It // doesn't actually mutate the state because it uses the Immer library, // which detects changes to a "draft state" and produces a brand new // immutable state based off those changes state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, }); // Action creators are generated for each case reducer function export const { increment, decrement, incrementByAmount } = counterSlice.actions; export default counterSlice.reducer;
Page
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import { Provider } from 'react-redux' import store from './store' import { useSelector, useDispatch } from "react-redux"; import { decrement, increment } from "./features/counter/counterSlice"; export function Counter() { const count = useSelector((state) => state.counter.value); const dispatch = useDispatch(); return ( <div> <div> <button aria-label="Increment value" onClick={() => dispatch(increment())} > Increment </button> <span>{count}</span> <button aria-label="Decrement value" onClick={() => dispatch(decrement())} > Decrement </button> </div> </div> ); } export default function ReduxDemo() { return ( <Provider store={store}> <Counter /> <Counter /> </Provider> ) }
迷思 回调 看起来 React单向数据流似乎很简洁, 但实际上 本身就有大量回调存在的可能性, 而这种回调在state变化状态下 竟然会变, 否则要自己加useCallback去管理, 感觉好蠢, 并且这样的设计下, 使用者可能有不需要的”回调”, 又变成在内部要判断未传递时的情况, 真的越想越蠢
children/slot 可能破坏 抽离等价 antd的Select, 下面的可以
1 2 3 <Select> {items.map(item => <Option key={item} value={item}>{item}</Option>)} </Select>
而下面的不行(看起来有所谓的抽离等价, 实际上配上Select具体内部实现又没有了抽离等价, 问题属于slot/children的问题)
1 2 3 4 5 const StringOption = ({value}) => <Option key={value} value={value}>{value}</Option>; <Select> {items.map(item => <StringOption value={item} />)} </Select>
for的key 大多数情况是浪费的?? vue 同