diff --git a/react/hooks-api-usage/readme.md b/react/hooks-api-usage/readme.md new file mode 100644 index 0000000..bd3e84c --- /dev/null +++ b/react/hooks-api-usage/readme.md @@ -0,0 +1,10 @@ +# Hooks API - Learning By Doing + +## 教材 + +- [Hooks API 索引](https://zh-hans.reactjs.org/docs/hooks-reference.html) +- [React Hooks 学习指南](https://fe.rualc.com/note/react-hooks.html) + +## Local Dev + +`parcel ./useContext/index.html` diff --git a/react/hooks-api-usage/useCallback/index.html b/react/hooks-api-usage/useCallback/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useCallback/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useCallback/script.tsx b/react/hooks-api-usage/useCallback/script.tsx new file mode 100644 index 0000000..66244c2 --- /dev/null +++ b/react/hooks-api-usage/useCallback/script.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { useCallback } from 'react'; +import ReactDOM from 'react-dom'; + +// * ------------------------------------------------ + +let ticker = 0; +let updateFlag = 0; + +const sameRandomFunctionFactory = () => { + const Rand = Math.random(); + return () => Rand; +}; + +// * ------------------------------------------------ + +const App = ({ updateFlag }) => { + const now = Date.now(); + + const cb = useCallback(sameRandomFunctionFactory(), [updateFlag]); + const result = cb(); + + return ( +
+

+ timer: +
+ {now} +

+

+ result of useCallback: ('updateFlag' props changes by 2 secs) +
+ {result} +

+
+ ); +}; + +// * ------------------------------------------------ + +setInterval(() => { + ticker += 1 / 2; + updateFlag = Math.round(ticker); + ReactDOM.render(, document.querySelector('#app')); +}, 1000); diff --git a/react/hooks-api-usage/useContext/index.html b/react/hooks-api-usage/useContext/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useContext/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useContext/script.tsx b/react/hooks-api-usage/useContext/script.tsx new file mode 100644 index 0000000..9ab62c5 --- /dev/null +++ b/react/hooks-api-usage/useContext/script.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { createContext, useState, useContext } from 'react'; +import ReactDOM from 'react-dom'; + +const genColor = () => `hsla(${Math.random() * 360}, 70%, 50%)`; + +// * ------------------------------------------------ + +type MyContext = { color: string; changer: (any) => void }; + +const ThemeContext = createContext(null); + +// * -------------------------------- + +const Comp = () => { + const { color, changer } = useContext(ThemeContext); + + return ( + + ); +}; + +// * -------------------------------- + +const App = () => { + const [state, setState] = useState({ color: 'green' }); + + const { color } = state; + const changer = () => setState({ color: genColor() }); + + return ( + + + + ); +}; + +// * ------------------------------------------------ + +ReactDOM.render(, document.querySelector('#app')); diff --git a/react/hooks-api-usage/useEffect/index.html b/react/hooks-api-usage/useEffect/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useEffect/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useEffect/script.tsx b/react/hooks-api-usage/useEffect/script.tsx new file mode 100644 index 0000000..eeb7ed2 --- /dev/null +++ b/react/hooks-api-usage/useEffect/script.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const App = () => { + const [count, setCount] = useState(0); + + useEffect(() => { + const timer = setTimeout(() => { + console.log(`You clicked ${count} times`); + }, 1000); + return () => { + clearTimeout(timer); + console.log(`cancel timer`); + }; + }); + + return ( +
+

You clicked {count} times

+ +
+ ); +}; + +ReactDOM.render(, document.querySelector('#app')); diff --git a/react/hooks-api-usage/useImperativeHandle/index.html b/react/hooks-api-usage/useImperativeHandle/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useImperativeHandle/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useImperativeHandle/script.tsx b/react/hooks-api-usage/useImperativeHandle/script.tsx new file mode 100644 index 0000000..b18d51c --- /dev/null +++ b/react/hooks-api-usage/useImperativeHandle/script.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; +import ReactDOM from 'react-dom'; + +// * ------------------------------------------------ + +const ChildInput = forwardRef((props, ref) => { + const realRef = useRef(null); + + useImperativeHandle(ref, () => realRef.current); + + return ; +}); + +// * -------------------------------- + +const App = () => { + const inputRef = useRef(null); + + useEffect(() => { + inputRef.current.focus(); + }, []); + + return ( +
+ +
+ ); +}; + +// * ------------------------------------------------ + +ReactDOM.render(, document.querySelector('#app')); diff --git a/react/hooks-api-usage/useLayoutEffect/index.html b/react/hooks-api-usage/useLayoutEffect/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useLayoutEffect/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useLayoutEffect/script.tsx b/react/hooks-api-usage/useLayoutEffect/script.tsx new file mode 100644 index 0000000..77cc0e8 --- /dev/null +++ b/react/hooks-api-usage/useLayoutEffect/script.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { useLayoutEffect, useRef, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const App = () => { + const [height, setHeight] = useState(0); + const refer = useRef(null); + + useLayoutEffect(() => { + const $dom = refer.current; + const domHeight = $dom.getBoundingClientRect().height; + if (height !== domHeight) setHeight(domHeight); + }); + + return ( +
+

hello

+

{height}

+
+ ); +}; + +ReactDOM.render(, document.querySelector('#app')); diff --git a/react/hooks-api-usage/useMemo/index.html b/react/hooks-api-usage/useMemo/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useMemo/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useMemo/script.tsx b/react/hooks-api-usage/useMemo/script.tsx new file mode 100644 index 0000000..c62064d --- /dev/null +++ b/react/hooks-api-usage/useMemo/script.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import { useMemo } from 'react'; +import ReactDOM from 'react-dom'; + +import { memoizeWith } from 'ramda'; +import { memoize } from 'lodash-es'; + +// * ------------------------------------------------ + +let ticker = 0; +let updateFlag = 0; + +const getRandom = () => Math.random(); + +// * try different tools, but react useMemo only keep the previous deps to compare +const randamMemo = memoizeWith(() => updateFlag, getRandom); +const lodashMemo = memoize(getRandom, () => updateFlag); + +// * ------------------------------------------------ + +const App = ({ updateFlag }) => { + const now = Date.now(); + + let result; + + { + result = useMemo(getRandom, [updateFlag]); + + // * try different tools, try declare here, they would fail + // result = randamMemo(); + // result = lodashMemo(); + } + + return ( +
+

+ timer: +
+ {now} +

+

+ result of useMemo: ('updateFlag' props changes by 2 secs) +
+ {result} +

+
+ ); +}; + +// * ------------------------------------------------ + +setInterval(() => { + ticker += 1 / 2; + updateFlag = Math.round(ticker); + ReactDOM.render(, document.querySelector('#app')); +}, 1000); diff --git a/react/hooks-api-usage/useReducer/index.html b/react/hooks-api-usage/useReducer/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useReducer/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useReducer/script.tsx b/react/hooks-api-usage/useReducer/script.tsx new file mode 100644 index 0000000..7917466 --- /dev/null +++ b/react/hooks-api-usage/useReducer/script.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { useReducer } from 'react'; +import ReactDOM from 'react-dom'; + +type MyReducer = React.Reducer<{ value: number }, { type: string; payload?: number }>; + +const reducer: MyReducer = (state, action) => { + const { value } = state; + const { type, payload = 1 } = action; + const nextValue = type === 'plus' ? value + payload : type === 'minus' ? value - payload : value; + return { value: nextValue }; +}; + +const App = () => { + const [state, dispatch] = useReducer(reducer, { value: 0 }); + + return ( + <> + Count by useReducer: {state.value} +
+ + + + ); +}; + +ReactDOM.render(, document.querySelector('#app')); diff --git a/react/hooks-api-usage/useRef/index.html b/react/hooks-api-usage/useRef/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useRef/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useRef/script.tsx b/react/hooks-api-usage/useRef/script.tsx new file mode 100644 index 0000000..e5f5ecb --- /dev/null +++ b/react/hooks-api-usage/useRef/script.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { useRef } from 'react'; +import ReactDOM from 'react-dom'; + +const App = () => { + const inputer = useRef(null); + + const logInputValue = () => console.log('input value:', inputer.current.value); + + return ( + <> + +
+ + + ); +}; + +ReactDOM.render(, document.querySelector('#app')); diff --git a/react/hooks-api-usage/useState/index.html b/react/hooks-api-usage/useState/index.html new file mode 100644 index 0000000..acb8681 --- /dev/null +++ b/react/hooks-api-usage/useState/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-api-usage/useState/script.tsx b/react/hooks-api-usage/useState/script.tsx new file mode 100644 index 0000000..ac725b6 --- /dev/null +++ b/react/hooks-api-usage/useState/script.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { useState } from 'react'; +import ReactDOM from 'react-dom'; + +const App = () => { + const [state, setState] = useState({ value: 0 }); + + const plusFn = () => setState({ value: state.value + 5 }); + const minusFn = () => setState((state) => ({ value: state.value - 3 })); + + return ( + <> + Count by useState: {state.value} +
+ + + + ); +}; + +ReactDOM.render(, document.querySelector('#app')); diff --git a/react/hooks-example/readme.md b/react/hooks-example/readme.md new file mode 100644 index 0000000..0fac331 --- /dev/null +++ b/react/hooks-example/readme.md @@ -0,0 +1,12 @@ +# Hooks Example - Learning By Doing + +## 教材 + +- [Hooks API 索引](https://zh-hans.reactjs.org/docs/hooks-reference.html) +- [Hooks API usage](../hooks-api-usage/) +- [示例 - React](https://zh-hans.reactjs.org/community/examples.html) +- [React Hooks 学习指南](https://fe.rualc.com/note/react-hooks.html) + +## Local Dev + +`parcel ./temp-transfer/index.html` diff --git a/react/hooks-example/temp-transfer/index.html b/react/hooks-example/temp-transfer/index.html new file mode 100644 index 0000000..4cca6dc --- /dev/null +++ b/react/hooks-example/temp-transfer/index.html @@ -0,0 +1,13 @@ + + + + + + + Document + + +
+ + + diff --git a/react/hooks-example/temp-transfer/script.tsx b/react/hooks-example/temp-transfer/script.tsx new file mode 100644 index 0000000..bac5d5d --- /dev/null +++ b/react/hooks-example/temp-transfer/script.tsx @@ -0,0 +1,75 @@ +// * https://zh-hans.reactjs.org/docs/lifting-state-up.html + +import * as React from 'react'; +import { useState } from 'react'; +import ReactDOM from 'react-dom'; + +// * ---------------------------------------------------------------- + +const toC = (fahrenheit) => ((fahrenheit - 32) * 5) / 9; + +const toF = (celsius) => (celsius * 9) / 5 + 32; + +const transTemp = (temperature, convert) => + Number.isNaN(parseFloat(temperature)) + ? '' + : (Math.round(convert(parseFloat(temperature)) * 1000) / 1000).toString(); + +const scaleNames = { + c: 'Celsius', + f: 'Fahrenheit', +}; + +// * ---------------------------------------------------------------- + +const BoilingVerdict = (props) => ( +

+ The water would {props.celsius >= 100 ? '' : 'not'} boil at {props.celsius} Celsius +

+); + +const TemperatureInput = ({ temperature, scale, onTemperatureChange }) => { + return ( +
+ Enter temperature in {scale}: + +
+ ); +}; + +// * -------------------------------- + +const Calculator = () => { + const [state, setState] = useState({ temperature: '', scale: 'c' }); + + const { scale, temperature } = state; + + const celsius = scale === 'c' ? temperature : transTemp(temperature, toC); + const fahrenheit = scale === 'f' ? temperature : transTemp(temperature, toF); + + const handler = (scale) => (e) => setState({ scale, temperature: e.target.value }); + const handleCelsiusChange = handler('c'); + const handleFahrenheitChange = handler('f'); + + return ( +
+ + + + + +
+ ); +}; + +const App = () => ; + +ReactDOM.render(, document.querySelector('#app')); diff --git a/react/hooks-example/unit-converter/readme.md b/react/hooks-example/unit-converter/readme.md new file mode 100644 index 0000000..84314e1 --- /dev/null +++ b/react/hooks-example/unit-converter/readme.md @@ -0,0 +1,13 @@ +# Unit Converter + +## Example From + +https://github.com/KarthikeyanRanasthala/react-unit-converter + +## Hooks Used + +- useState + +## Dev + +`parcel ./src/index.html` diff --git a/react/hooks-example/unit-converter/src/components/app.jsx b/react/hooks-example/unit-converter/src/components/app.jsx new file mode 100644 index 0000000..1a06248 --- /dev/null +++ b/react/hooks-example/unit-converter/src/components/app.jsx @@ -0,0 +1,119 @@ +import React, { useState } from 'react'; + +import convert from 'convert-units'; + +import Container from '@material-ui/core/Container'; +import Grid from '@material-ui/core/Grid'; + +import { Selector } from './selector'; +import { InputField } from './input'; + +// * ------------------------------------------------ + +const updateCalc = (name, { leftType, rightType, leftValue, rightValue }) => { + if ([leftType, rightType, leftValue, rightValue].filter((e) => !e).length > 1) return {}; + + const fromLeft = name === 'leftValue' || name === 'rightType'; + return fromLeft + ? { + rightValue: convert(leftValue) + .from(leftType) + .to(rightType), + } + : { + leftValue: convert(rightValue) + .from(rightType) + .to(leftType), + }; +}; + +const handleMesureFactory = (setState) => (event) => { + const { name, value } = event.target; + + setState((state) => ({ + ...state, + ...emptyState, + [name]: value, + })); +}; + +const emptyState = { + Measurement: '', + leftType: '', + rightType: '', + leftValue: '', + rightValue: '', +}; + +const handleSelectorFactory = (setState) => (event) => { + const { name, value } = event.target; + + setState((state) => { + const nextState = { ...state, [name]: value }; + const calcPatch = updateCalc(name, nextState); + return { ...nextState, ...calcPatch }; + }); +}; + +// * ------------------------------------------------ + +export const App = () => { + const [state, setState] = useState(emptyState); + + const handleMesure = handleMesureFactory(setState); + const handleSelector = handleSelectorFactory(setState); + + return ( + + + + {state.Measurement ? ( + <> + + + + ) : ( + <> + )} + {state.leftType && state.rightType ? ( + <> + + + + ) : ( + <> + )} + + + ); +}; diff --git a/react/hooks-example/unit-converter/src/components/input.jsx b/react/hooks-example/unit-converter/src/components/input.jsx new file mode 100644 index 0000000..768976f --- /dev/null +++ b/react/hooks-example/unit-converter/src/components/input.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import TextField from '@material-ui/core/TextField'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import Grid from '@material-ui/core/Grid'; + +export const InputField = ({ label, value, name, onChange }) => { + return ( + + {label}, + }} + /> + + ); +}; diff --git a/react/hooks-example/unit-converter/src/components/selector.jsx b/react/hooks-example/unit-converter/src/components/selector.jsx new file mode 100644 index 0000000..16b9468 --- /dev/null +++ b/react/hooks-example/unit-converter/src/components/selector.jsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import Grid from '@material-ui/core/Grid'; +import FormControl from '@material-ui/core/FormControl'; +import InputLabel from '@material-ui/core/InputLabel'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; + +export const Selector = ({ list = [], value, mdWidth, label, name, onChange }) => { + return ( + + + {label} + + + + ); +}; diff --git a/react/hooks-example/unit-converter/src/index.html b/react/hooks-example/unit-converter/src/index.html new file mode 100644 index 0000000..5ef9983 --- /dev/null +++ b/react/hooks-example/unit-converter/src/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +
+ + + + diff --git a/react/hooks-example/unit-converter/src/script.js b/react/hooks-example/unit-converter/src/script.js new file mode 100644 index 0000000..6647c0f --- /dev/null +++ b/react/hooks-example/unit-converter/src/script.js @@ -0,0 +1,6 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { App } from './components/app'; + +ReactDOM.render(, document.querySelector('#app')); +