1 使用了react官方腳手架:create-react-app
https://github.com/facebook/create-react-app
npm run eject 可以打開配置文件
自定義配置文件
執行安裝: npx create-react-app ts-with-react --typescript
npx 只有在npm5.2以上版本才有
1、避免安裝全局模塊:臨時命令,使用后刪除,再次執行的時候再次下載
2、調用項目內部安裝的模塊用起來更方便:比如 在package.json文件中安裝了一個依賴:mocha,如果想執行有兩種方法:
2.1 在scripts中定義
{ "script":{ "test":"mocha --version" } }
2.2 在命令行中執行 node_modules/.bin/mocha --version
而使用npx的話 則只需要執行: npx mocha --version
首先新建一個hello組件:components/hello.jsx
import React from 'react' interface IHelloProps { message?: string;//因為設置了props的默認值,這里用?表示有無都可 } /* const Hello = (props:IHelloProps) => { return <h2>{props.message}</h2> } */ const Hello: React.FC<IHelloProps> = (props) => { //FC是 react 官方提供的一個 interface 的簡寫:FunctionComponent,接收一個范型 //這樣Hello包含了很多靜態方法 return <h2>{props.message}</h2> } Hello.defaultProps = { message: "Hello World" } export default Hello
## 什么是react hook
React 16.8 帶來的全新特性,使用函數式組件,即將替代class組件的寫法;
解決的問題
1.組件很難復用狀態邏輯,一般使用HOC或者render Props
2.復雜組件難以理解,尤其是生命周期函數
例如,獲取props中的id,需要在 componentDidMount和 componentDidUpdate 中同時定義;監聽函數也需要在 componentDidMounted和componentWillUnmount中監聽和注銷
3.react組件一直是函數,使用Hook完全擁抱函數
## State Hook
新建一個likeBotton.tsx 文件【點贊按鈕】
import React, { useState } from 'react' const LikeButton: React.FC = () => { const [like, setLike] = useState(0); const [on,setOn] = useState(true); return ( <> <button onClick={() => {setLike(like + 1)}}> {like} 👍 </button> <button onClick={() => {setOn(!on)}}> {on?'ON':'OFF'} </button> </> ) } export default LikeButton
網絡請求、DOM操作 和函數渲染頁面關系不大的 放在副作用中
Effect Hook
1.無需清除的Effect
2.需要清除的Effect
如果是以前的話,需要在兩個生命周期中執行同樣的代碼
componentDidMount(){ document.title = `you clicked ${this.state.count} times`; } componentDidUpdate(){ document.title = `you clicked ${this.state.count} times`; }
使用useEffect:
import React, { useState,useEffect } from 'react' const LikeButton: React.FC = () => { const [like, setLike] = useState(0); const [on,setOn] = useState(true); useEffect(()=>{//useEffect會在組件每次渲染的時候,都會被調用 document.title = `點擊了${like}次` }) return ( <> <button onClick={() => {setLike(like + 1)}}> {like} 👍 </button> <button onClick={() => {setOn(!on)}}> {on?'ON':'OFF'} </button> </> ) } export default LikeButton
2.需要清除的Effect
功能: 使用useEffect 完成一個鼠標跟蹤器
原來在class中的實現方法:
componentDidMount(){ document.addEventListener('click',this.updateMouse); } componentWillUnMount(){ document.addEventListener('click',this.updateMouse); }
使用useEffect,就要忘記以前的生命周期,頁面掛載之后執行useEffect
import React, { useState, useEffect } from 'react' const MouseTracker: React.FC = () => { const [ positions, setPositions ] = useState({x: 0, y: 0}) useEffect(() => { console.log('add effect', positions.x)//執行順序2:add effect 0;//執行順序6:add effect 102 const updateMouse= (e: MouseEvent) => { console.log('inner') //執行順序3 setPositions({ x: e.clientX, y: e.clientY }) } document.addEventListener('click', updateMouse) return () => { //如果不加return函數中卸載功能,每次渲染函數組件的時候,都會執行useEffect //導致注冊很多個 監聽事件, console.log('remove effect', positions.x) //執行順序5:remove effect 0 document.removeEventListener('click', updateMouse) } }, []) //執行順序1:before render 0//點擊頁面后,執行順序4:before render 102 console.log('before render', positions.x) return ( <p>X: {positions.x}, Y : {positions.y}</p> ) } export default MouseTracker
但是每次更新函數組件,都要執行useEffect ,下面介紹如何控制useEffect函數的執行
useEffect(() => { console.log('document title effect is running') document.title = `點擊了${like}次`; document.addEventListener('click', updateMouse) return ()=>{ document.removeEventListener('click', updateMouse) } }, []) //useEffect第二個參數,如果是空數組,表示只在掛載元素后執行1次,但是其返回函數,會在組件卸載的時候執行 //如果不寫第二個參數,則不做限制,組件中的任何一個data變化的時候,都會執行useEffect函數 //如果第二個參數寫上變量后,則只有該變量發生變化的時候,才執行 useEffect
## 自定義Hook——新建的hooks,必須以 use 開頭
1 將組件邏輯提取到可重用的函數中
例如兩個組件中均用到了公共的邏輯,獲取跟隨鼠標移動的坐標:
新建hooks/useMousePosition.jsx
import React, { useState, useEffect } from 'react' const useMousePosition = () => { const [ positions, setPositions ] = useState({x: 0, y: 0}) useEffect(() => { console.log('add effect', positions.x) const updateMouse= (e: MouseEvent) => { setPositions({ x: e.clientX, y: e.clientY }) } document.addEventListener('mousemove', updateMouse) return () => { console.log('remove effect', positions.x) document.removeEventListener('mousemove', updateMouse) } }, []) return positions } export default useMousePosition
在其他組件中引入方式:
import useMousePosition from './hooks/useMousePosition' const App:React.FC = () => { const positions = useMousePosition(); return ( <div> <p>X:{position.x},Y:{positions.y}</p> </div> ) }
## 編輯一個hooks,功能是:加載圖片功能,替換之前的HOC方式,簡單方便,相當於定義一個函數,然后再外面用到的地方調用,可以傳參,可以控制調用的時機:
interface IShowResult{ message:string, status:string } //定義一個組件 const DogShow:React.FC<{data:IShowResult}> = ({data})=>{ return ( <> <h2>show:{data.status}</h2> <img src={data.message}/> </> ) } const App:React.FC=()=>{ //高階組件,將一個組件用參數形式傳入,然后經過包裹后返回一個新的組件,達到公用包裹組件的功能 const WrappedDogShow = withLoader(DogShow,'https://dog.ceo/api/breeds/image/random'); return ( <WrappedDogShow/> ) }
高階組件
// high order component import React from 'react' import axios from 'axios' interface ILoaderState { data: any, isLoading: boolean } interface ILoaderProps { data: any, } const withLoader = <P extends ILoaderState>(WrappedComponent: React.ComponentType<P>, url: string) => { return class LoaderComponent extends React.Component<Partial<ILoaderProps>, ILoaderState> { constructor(props: any) { super(props) this.state = { data: null, isLoading: false } } componentDidMount() { this.setState({ isLoading: true, }) axios.get(url).then(result => { this.setState({ data: result.data, isLoading: false }) }) } render() { const { data, isLoading } = this.state return ( <> { (isLoading || !data) ? <p>data is loading</p> : <WrappedComponent {...this.props as P} data={data} /> } </> ) } } } export default withLoader
//父組件 <template> <Loading><DogImg/></Lading> </template> //子組件 <template> <div> <p v-if="load">loading...</p> <div v-else><slot><img src="/location.png"/></slot></div> </div> </template> <script> { //需要傳給父組件的data,使用 v-model 或者 vuex均可 } </script>
PS:react父子組件使用vue中的slot
import React, { Component } from 'react'; import Children from './Children'; class Father extends Component { render() { return ( <div> <Children> <em>1111111111111111</em> </Children> <p>我是父組件</p> </div> ) } } export default Father;
子組件
import React, { Component } from 'react'; class Children extends Component{ constructor(props){ super(props) } render(){ return ( <div> {this.props.children //這里調用了其內部} <h1>我是子組件</h1> </div> ) } } export default Children;
import { useState, useEffect } from 'react' import axios from 'axios' const useURLLoader = (url: string, deps: any[] = []) => { //第二個入參:deps,是決定該函數什么時候更新,因為放在了 useEffect 的第二個參數中 //默認是空數組,也就是只執行一次 const [data, setData] = useState<any>(null) const [loading, setLoading] = useState(false) useEffect(() => { setLoading(true) axios.get(url).then(result => { setData(result.data) setLoading(false) }) }, deps) return [data, loading] } export default useURLLoader
import useURLLoader from './hooks/useURLLoader' interface IShowResult { message: string; status: string; } const App: React.FC = () => { const [show,setShow] = useState(true) const [data, loading] = useURLLoader('http://www.xxx.com',[show]);//每次show變化的時候,都要更新hooks中的useEffect函數 const dogResult = data as IShowResult; return ( <div className="App"> { loading?<p>圖片下載中。。。</p> :<img src={dogResult && dogResult.message}/> } <p> <button onClick={() => {setShow(!show)}}>Refresh dog photo</button> </p> </div> ); } export default App;
useRef的使用
先來看 state和props的每次改變,其實都相當於閉包,每次的數據都是獨立的:
比如下面代碼,點擊了第一個button后,三秒后才彈出 like 的值;在此期間多次點擊第二個按鈕,當前的 like 已經是其他的數字,但是異步執行的時候,保留的仍然是當時的數值;
import React, { useState, useEffect } from 'react' const LikeButton: React.FC = () => { const [like, setLike] = useState(0) useEffect(() => { console.log('document title effect is running') document.title = `點擊了${like}次` }, [like]) function handleAlertClick() { setTimeout(() => { alert('you clicked on ' + like) }, 3000) } return ( <> <button onClick={() => {setLike(like + 1)}}> {like} 👍 </button> <button onClick={handleAlertClick}> Alert! </button> </> ) } export default LikeButton
所以使用useRef,注意的是修改ref的值並不會觸發 render函數的更新,之所以函數更新是因為修改了 useState的值
import React, { useState, useEffect, useRef } from 'react' const LikeButton: React.FC = () => { const [like, setLike] = useState(0);//state在每一個render中都是獨立的值,相當於閉包的存在 const likeRef = useRef(0);//useRef 是一個函數,初始值是0;ref在所有的render中都保持的唯一的引用 useEffect(() => { console.log('document title effect is running') document.title = `點擊了${like}次` }, [like]) function handleAlertClick() { setTimeout(() => { alert('you clicked on ' + likeRef.current)//改動了這里,以current獲取值 }, 3000) } return ( <> <button onClick={() => {setLike(like + 1;likeRef.current++)}}> {like} 👍 </button> <button onClick={handleAlertClick}> Alert! </button> </> ) } export default LikeButton
import React, { useState, useEffect, useRef } from 'react' const LikeButton: React.FC = () => { const [like, setLike] = useState(0); const didMountRef = useRef(false); useEffect(()=>{ //只有在數據更新的時候,才去執行 if(didMountRef.current){ console.log('is update'); }else { didMountRef.current = true; } }) return ( <> <button onClick={() => {setLike(like + 1)}}> {like} 👍 </button> <button onClick={handleAlertClick}> Alert! </button> </> ) } export default LikeButton
useRef常用來訪問節點:
import React, { useState, useEffect, useRef } from 'react' const LikeButton: React.FC = () => { const [like, setLike] = useState(0); const domRef = useRef<HTMLInputElement>(null);//雖然初始化的時候是null,但是其實是DOM元素的范性 useEffect(()=>{ //每次渲染render的時候后會去執行,一旦獲取到dom元素,則獲取光標,而用 useRef 定義的DOM元素,是唯一的 if(domRef && domRef.current){ domRef.current.focus() } }) return ( <> <input type="text" ref = {domRef}/> <button onClick={() => {setLike(like + 1)}}> {like} 👍 </button> <button onClick={handleAlertClick}> Alert! </button> </> ) } export default LikeButton
使用useContext解決數據多層透傳的問題
首先在最外層,父組件中使用:
1 import React, { useState } from 'react'; 2 import LikeButton from './components/LikeButton' 3 import Hello from './components/Hello' 4 import useURLLoader from './hooks/useURLLoader' 5 6 interface IThemeProps { 7 [key: string]: {color: string; background: string;} 8 } 9 const themes: IThemeProps = { 10 'light': { 11 color: '#000', 12 background: '#eee', 13 }, 14 'dark': { 15 color: '#fff', 16 background: '#222', 17 } 18 } 19 export const ThemeContext = React.createContext(themes.light) //定義context,且使用export導出 20 const App: React.FC = () => { 21 const [ show, setShow ] = useState(true) 22 //使用 ThemeContext.Provider 包裹所有的組件 23 return ( 24 <div className="App"> 25 <ThemeContext.Provider value={themes.dark}> 26 <header className="App-header"> 27 <img src={logo} className="App-logo" alt="logo" /> 28 <LikeButton /> 29 <Hello /> 30 </header> 31 </ThemeContext.Provider> 32 </div> 33 ); 34 } 35 36 export default App;
import React, { useState, useEffect, useRef, useContext } from 'react'//導入useContext import { ThemeContext } from '../App' //自組件使用,首先要調用context const LikeButton: React.FC = () => { const theme = useContext(ThemeContext) const style = { background: theme.background, color: theme.color, } return ( <> <input type="text" ref={domRef} /> <button style={style}> {like} 👍 </button> </> ) } export default LikeButton