fs react 使用 保存文件_入门TypeScript编写React
使用 create-react-app 開啟 TypeScript
Create React App 是一個官方支持的創建 React 單頁應用程序的CLI,它提供了一個零配置的現代構建設置。當你使用 Create React App 來創建一個新的 TypeScript React 工程時,你可以運行:
$ npx create-react-app my-app --typescript $ # 或者 $ yarn create react-app my-app --typescript如果在已有的工程中添加,也非常簡單:
$ npm install --save typescript @types/node @types/react @types/react-dom @types/jest $ # 或者 $ yarn add typescript @types/node @types/react @types/react-dom @types/jest從零配置
創建 index.html 文件,以及src 目錄,在 src目錄中創建 index.tsx。
TypeScript 的文件格式是 tsx接下來安裝必要的包和配置 package.json 文件:
"scripts": {"dev": "MODE=development webpack -w --mode=development","build": "MODE=production webpack --mode=production" }, "dependencies": {"@types/react": "^16.8.13","@types/react-dom": "^16.8.3","react": "^16.8.6","react-dom": "^16.8.6" }, "devDependencies": {"awesome-typescript-loader": "^5.2.1","source-map-loader": "^0.2.4","typescript": "^3.4.3","webpack": "^4.29.6","webpack-cli": "^3.3.0" }創建 tsconfig.json 和 webpack.config.js 文件:
{"compilerOptions": {"target": "es5","module": "commonjs","lib": ["dom","es2015"],"jsx": "react","sourceMap": true,"strict": true,"noImplicitAny": true,"baseUrl": "src","paths": {"@/*": ["./*"],},"esModuleInterop": true,"experimentalDecorators": true,},"include": ["./src/**/*"] }- jsx 選擇 react
- lib 開啟 dom 和 es2015
- include 選擇我們創建的 src 目錄
類組件的使用
類組件是目前來說使用的最頻繁的一種,因此我們需要了解到它。
Props 和 State
首先創建 Props 和 State 接口,Props 接口接收一個 name 參數,State 接口接收 color:
interface IProps {name: string; }interface IState {color: "red" | "blueviolet" } class Home extends React.Component<IProps, IState> {constructor(props: IProps){super(props);this.state = {color: "red"}}public onClickColor = () => {const { color } = this.state;if (color === "red") {this.setState({color: "blueviolet"});}if (color === "blueviolet") {this.setState({color: "red"});}}public render(){const { name } = this.props;const { color } = this.state;return (<div><span style={{ color }}>{ name }</span><button onClick={this.onClickColor}>變顏色</button></div>);} }export default Home;如圖:
在 App 中使用 Home 組件時我們可以得到明確的傳遞參數類型。
處理 Event 對象
有時候我們需要處理一下 Event 對象,一般 change 事件我們可以使用 React.ChangeEvent,click 事件可以使用 React.MouseEvent ,它們都接收一個 Element,如:
onClickColor = (ev: React.MouseEvent<HTMLButtonElement>) => {// }PureComponent
我們都知道 React 的刷新機制,因此如果每一次的變動都要刷新一下界面,這對于應用程序的性能來說是一個非常不科學的事情,因此在沒有 PureComponent 之前,我們都需要手動使用 shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): boolean; 來確認到底要不要刷新界面,如:
import * as React from "react"; import Typography from "@material-ui/core/Typography";interface IMyComparisonProps {text: string; }class MyComparison extends React.Component<IMyComparisonProps> {constructor(props: IMyComparisonProps) {super(props);}public shouldComponentUpdate(nextProps: IMyComparisonProps) {if (this.props.text === nextProps.text) {return false;}return true;}public render() {const { text } = this.props;return (<Typography>Component 值:{ text }</Typography>);} }export default MyComparison;如果返回的是 false 那么將不調用 render,如果是 true 則調用 render。
但是如果我們使用 PureComponent 那么就省略了這一步,我們可以不用關心組件是否要刷新,而是 React.PureComponent 來幫我們決定。在使用之前,我們還有一些注意事項要了解,React.PureComponent 是一個和 React.Component 幾乎相同,唯一不同的是 React.PureComponent 幫助我們完成了 shouldComponentUpdate 的一些交淺的比較,因此在我們真實的組件設計中,我們一般會用于最后一個關鍵點的組件上。
Portals
ReactDOM 中提供了一個方法 createPortal,可以將節點渲染在父組件之外,但是你可以依然使用父組件上下文中的屬性。這個特性在我所講的全局對話框或者提示框中非常有用,它脫離了父節點的容器,插在最外層,在樣式上就能通過 position: fixed 來覆蓋整個文檔樹。
我們在 state 中定義了一個 open,它只接收一個布爾值,用于打開提示框或關閉提示框架,如:
export interface IPortalsProps {}export interface IPortalsState {open: boolean; }然后我們定義兩個方法用于設置 open:
public clickHandler = () => {this.setState({open: true,}); }public clickHandlerClose = () => {this.setState({open: false,}); }最后在 render 方法中使用 ReactDOM.createPortal 來創建一個全局的 Alert,如:
import * as React from "react"; import * as ReactDOM from "react-dom"; import Button from "@material-ui/core/Button"; import Alert from "../Alert"; import {IPortalsProps,IPortalsState, } from "./types";class MyPortals extends React.Component<IPortalsProps, IPortalsState> {constructor(props: IPortalsProps) {super(props);this.state = {open: false,};}public clickHandler = () => {this.setState({open: true,});}public clickHandlerClose = () => {this.setState({open: false,});}public render() {const { open } = this.state;return (<div><Buttonvariant="outlined"color="primary"onClick={this.clickHandler}>提示</Button>{ReactDOM.createPortal(<Alertopen={open}message="React Component Portals Use"handleClose={this.clickHandlerClose}/>,document.getElementById("app")!,)}</div>);} }export default MyPortals;Fragments
Fragments 可以讓我們減少生成過多有副作用的節點,以往 render 必須返回單一節點,因此很多組件常常會產生過多無用的 div,React 根據這樣的情況給予了一個組件來解決這個問題,它就是 Fragment。
public render(){return (<React.Fragment><div></div><div></div></React.Fragment>) }//orpublic render(){return (<><div></div><div></div></>) }函數組件以及 Hooks
Hooks 自去年10月發布以來,函數組件就派上了用場,React 的函數組件主要引用 SFC 返回(React.FunctionComponent),當然你也可以不引用 SFC 類型只不過返回的是(JSX.Element),這就是區別。
useState
以前:
interface IFuncComp {name: string; } const FuncComp: React.SFC<IFuncComp> = ({ name }) => {return (<div>{ name }</div>) }現在:
interface IFuncComp2 {name: string; }const FuncComp2: React.SFC<IFuncComp2> = ({ name }) => {const [ num, setNum ] = React.useState<number>(0);return (<div>{ name } { num }<button onClick={() => {setNum(num + 1);}}>+</button></div>) } function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];由于 useState 被定義為一個泛型函數,因此類型可以由我們自己來指定。
useEffect
當你使用 useEffect 時,我們可以傳入第三個參數來決定是否執行這個 callback ,這對于優化你的應用至關重要。
React.useEffect(() => {}, [num]);useContext
對于 useContext 當你需要共享數據時可用:
interface IContext {name: string; } const initContext: IContext = {name: "", }; const context = React.createContext(initContext);const FuncMainContext = () => {return (<><context.Provider value={initContext}><FuncContext /></context.Provider></>) }const FuncContext = () => {const va = React.useContext(context);return (<div>{ va.name }</div>) }useReducer
如果你已經習慣 redux 不妨來看看 useReducer,假設我們需要通過按鈕來更改文本顏色:
interface IState {color: "red" | "blueviolet" }interface IAction {type: string;payload: any; }const reducer = (prevState: IState, action: IAction) => {const { type, payload } = action;switch(type){case "COLOR_CHANGE" : {return { ...prevState, color: payload };}default: {return prevState;}} }const App = () => {const initialState: IState = {color: "red"}const [state, dispatch ] = React.useReducer(reducer, initialState);return (<div><span style={{ color: state.color }}>icepy</span><button onClick={() => {dispatch({type: "COLOR_CHANGE",payload: state.color === "red" ? "blueviolet" : "red"});}}>change</button></div>); }useRef
當我們需要來引用原生DOM來處理某件事情時,useRef 可以輔助我們完成這項工作:
const App = () => {const inputEl = React.useRef<HTMLInputElement>(null);const onButtonClick = () => {if (inputEl && inputEl.current) {inputEl.current.focus();}}return (<><input ref={inputEl} type="text" /><button onClick={onButtonClick}>Focus</button></>); }useMemo
接下來我們可以說一說 useMemo ,這只能當作一次性能優化的選擇,通常情況下假設我們的 state 有兩個屬性,它的場景可能如下:
const App = () => {const [ index, setIndex ] = React.useState<number>(0);const [ str, setStr ] = React.useState<string>("");const add = () => {return index * 100;}return (<><div>{index}-{str}-{add()}</div><div><button onClick={() => {setIndex(index + 1);}}>+</button><input type="text" onChange={(ev: React.ChangeEvent<HTMLInputElement>) => {setStr(ev.target.value);}}/></div></>); }無論如何修改 index 或 str 都會引發 add() 的執行,這對于性能來說是很難接受的,因為 add() 只依賴于 index ,因此我們可以使用 useMemo 來優化此項。
const App = () => {const [ index, setIndex ] = React.useState<number>(0);const [ str, setStr ] = React.useState<string>("");const add = React.useMemo(() => {return index * 100;}, [index]);return (<><div>{index}-{str}-{add}</div><div><button onClick={() => {setIndex(index + 1);}}>+</button><input type="text" onChange={(ev: React.ChangeEvent<HTMLInputElement>) => {setStr(ev.target.value);}}/></div></>); }useMemo 的類型依賴于 factory 的返回值,我們可以觀察一下它的描述文件:
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;useCallback
那么 useCallback 的使用和 useMemo 比較類似,但它返回的是緩存函數。 通常情況下,我們可以使用 useCallback 來處理父組件更新但不想子組件更新的問題,如:
interface IAppChildProps {callback: () => number; } const AppChild = ({ callback }: IAppChildProps) => {const [ index, setIndex ] = React.useState(() => callback());React.useEffect(() => {setIndex(callback());}, [callback])return (<div> { index }</div>); }const App = () => {const [ index, setIndex ] = React.useState<number>(0);const [ str, setStr ] = React.useState<string>("");const callback = React.useCallback(() => {return index * 100;}, [index]);return (<><h1>{ str }</h1><AppChild callback={callback} /><div><button onClick={() => {setIndex(index + 1);}}>+</button><input type="text" onChange={(ev: React.ChangeEvent<HTMLInputElement>) => {setStr(ev.target.value);}}/></div></>); }useImperativeHandle
useImperativeHandle 可以讓你使用 ref 將自定義的函數暴露給父組件,這種場景一般情況可以用于在父組件中操作子組件的DOM元素,需要和 forwardRef 配合使用:
interface IFancyInput {name: string; }interface IFancyInputRef {focus: () => void; }const fancyInput = (props: IFancyInput, ref: React.Ref<IFancyInputRef>) => {const inputEl = React.useRef<HTMLInputElement>(null);React.useImperativeHandle(ref, () => ({focus: () => {if (inputEl && inputEl.current) {inputEl.current.focus();}}}));return (<input ref={inputEl} type="text" defaultValue={props.name}/>); }const FancyInput = React.forwardRef<IFancyInputRef, IFancyInput>(fancyInput);const App = () => {const fancyRef = React.useRef<IFancyInputRef>(null);return (<div><FancyInput ref={fancyRef} name="icepy" /><button onClick={() => {if (fancyRef && fancyRef.current) {fancyRef.current.focus();}}}>+</button></div>) }在組件樹之間傳遞數據的 Context
在一個典型的 React 應用中,數據都是通過 Props 屬性自上而下進行傳遞的,但某些情況下這些屬性有多個組件需要共享,那么 Context 就提供了這樣一種共享的方式。
當你使用 createContext 創建一個 Context 時它會返回一個 React.Context<T> 類型。
每一個 Context 對象都會返回一個 Provider 組件,它允許消費組件訂閱 context 的變化,當 Provider 的value 發生變化時,它內部的所有消費組件都將重新渲染。
interface IContext {name: string; } const initContext:IContext = {name: "", }; const Context = React.createContext(initContext);const AppChild = () => {const context = React.useContext(Context);return (<div>{context.name}</div>) }const AppChild1 = () => {const context = React.useContext(Context);return (<div>{context.name}</div>) } const App = () => {const [ name, setName ] = React.useState("");return (<div><Context.Provider value={{ name }}><AppChild /><AppChild1 /></Context.Provider><button onClick={() => {setName("icepy");}}>+</button></div>) }我們也可以看一個類組件的例子:
interface IContext {name: string; } const initContext:IContext = {name: "", }; const Context = React.createContext(initContext);class AppChild extends React.Component {static contextType = Context;public render(){const { name } = this.context;return (<div> { name }</div>)} } const App = () => {const [ name, setName ] = React.useState("");return (<div><Context.Provider value={{ name }}><AppChild /></Context.Provider><button onClick={() => {setName("icepy");}}>+</button></div>) }在 TypeScript 中 Context 支持的并不算太好,如:
static contextType?: Context<any>; /*** If using the new style context, re-declare this in your class to be the* `React.ContextType` of your `static contextType`.** ```ts* static contextType = MyContext* context!: React.ContextType<typeof MyContext>* ```** @deprecated if used without a type annotation, or without static contextType* @see https://reactjs.org/docs/legacy-context.html*/ // TODO (TypeScript 3.0): unknown context: any;Ref 和 DOM
Refs 提供了一種方式,允許我們訪問 DOM 節點或在 render 方法中創建的 React 元素。
const App = () => {const but = React.createRef<HTMLButtonElement>();return (<div><button ref={but} onClick={() => {if (but && but.current) {if (but.current.nodeName === "BUTTON") {alert("BUTTON");}}}}> + </button></div>) }獲取 React 對象:
class AppChild extends React.Component {public onButtonClick = (target: EventTarget) => {console.dir(target);}public render(){return (<div>1234</div>)} }const App = () => {const appChild = React.createRef<AppChild>();return (<><AppChild ref={appChild}/><button onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {if (appChild && appChild.current) {appChild.current.onButtonClick(ev.target);}}}>+</button></>) }ref 也可以傳遞函數:
const App = () => {const inputCallback = (el: HTMLInputElement) => {console.log(el);}return (<div><input ref={inputCallback}/></div>) }對應的 useRef() 也非常類似,它可以很方便的保存任何可變值,這是因為它創建的是一個普通 JavaScript 對象。
const App = () => {const inputEl = React.useRef<HTMLInputElement>(null);return (<div><input ref={inputEl} type="text"/><button onClick={() => {if (inputEl && inputEl.current) {inputEl.current.focus();}}}>+</button></div>) }React 頂層其他 APIs
React 是整個 React 庫的入口,頂層 APIs 中除了我們比較熟悉的如 Component 之外還有一些比較有用的,這里會介紹幾種我們不常用但非常重要的頂層 APIs。
isValidElement
驗證對象是否為 React 對象,返回值是 true 或 false:
React.isValidElement(object);cloneElement
有時我們會遇到這樣一個場景,就是 tabs 選項卡,對于它的設計我們可能會有一個預期,做一個簡單版,比如:
<Tabs value={index} onChange={(value) => {setIndex(value); }}><Tab value={1}>Tab 1</Tab><Tab value={2}>Tab 2</Tab><Tab value={3}>Tab 3</Tab> </Tabs> <div style={{ display: index === 1 ? "block": "none"}}>1</div> <div style={{ display: index === 2 ? "block": "none"}}>2</div> <div style={{ display: index === 3 ? "block": "none"}}>3</div>點擊 Tab 的時候需要把它的 onClick 事件替換成 Tabs 的 onChange,因此這里會使用到 cloneElement 方法來處理。
interface ITabsProps {value: number;onChange: (value: number) => void;children?: React.ReactNode; }const tabsStyles: React.CSSProperties = {width: "100%",display: "flex",flexDirection: "row", }const Tabs = (props: ITabsProps) => {const onChange = (value: number) => {props.onChange(value);}const renderTab = () => {const { children } = props;if (children && Array.isArray(children)) {const arrayChilds = children.map((v, i) => {if (React.isValidElement(v)) {const childrenProps = {onChange,key: `Tab-${i}`,};return React.cloneElement(v, childrenProps);}});return arrayChilds;}if (children && !Array.isArray(children)) {const childrenProps = {onChange,key: "Tab",};if (React.isValidElement(children)) {return React.cloneElement(children, childrenProps);}}}return (<div style={tabsStyles}>{renderTab()}</div>); }由于我們把 childrenProps 替換了,因此子元素的 Tab 就可以如此:
interface ITabProps {value: number;onChange?: (value: number) => void;children?: React.ReactNode; }const tabStyles: React.CSSProperties = {width: "50px",marginRight: "10px",border: "1px solid red",textAlign: "center",cursor: "pointer" }const Tab = (props: ITabProps) => {const changeHandler = () => {const { onChange, value } = props;if (onChange) {onChange(value);}}return (<divstyle={tabStyles}onClick={changeHandler}>{ props.children }</div>); }memo
React.memo 為高階組件。它與 React.PureComponent 非常相似,但它適用于函數組件,但不適用于 class 組件。
此方法僅作為性能優化的方式而存在。interface IProps {value: number; }const AppChild = (props: IProps) => {return (<div>props.value: { props.value}</div>) }const MemoAppChild = React.memo(AppChild);interface IState {date: Date;value: number; }class App extends React.Component<{}, IState> {constructor(props: {}){super(props);this.state = {value: 0,date: new Date(),}}public componentDidMount(){setInterval(()=>{this.setState({date:new Date()})},1000)}public render(){return (<div><MemoAppChild value={this.state.value} /><div>{ this.state.date.toString() }</div></div>);} }如果你想更細節的控制,可以傳入第二個參數,它是一個函數:
interface IProps {value: number; }const AppChild = (props: IProps) => {return (<div>props.value: { props.value}</div>) }type Equal = (prevProps: IProps, nextProps: IProps) => boolean;const areEqual: Equal = (prevProps, nextProps) => {if (prevProps.value === nextProps.value) {return true;} else {return false;} } const MemoAppChild = React.memo(AppChild, areEqual);interface IState {date: Date;value: number; }class App extends React.Component<{}, IState> {constructor(props: {}){super(props);this.state = {value: 0,date: new Date(),}}public componentDidMount(){setInterval(()=>{this.setState({date:new Date()})},1000)}public render(){return (<div><MemoAppChild value={this.state.value} /><div>{ this.state.date.toString() }</div></div>);} }總結
以上是生活随笔為你收集整理的fs react 使用 保存文件_入门TypeScript编写React的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 发送16进制数据'_java
- 下一篇: html5游戏开发box2djs,Box