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方式,简单方便,相当于定义一个函数,然后再外面用到的地方调用,可以传参,可以控制调用的时机:
react 中的 HOC 高阶组件,就是一个函数,接受一个组件作为参数,返回一个新的组件;
react可以通过高阶组件来扩展,而vue需要通过mixins来扩展。
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
功能是传入一个组件,在高阶组件中,有三个功能
1、发送传入的url请求,得到需要的data值;
2、有一定的判断逻辑,没有拿到数据的时候显示 <p>,否则显示 传入的组件
3、给传入的组件添加data值
Vue中如何实现类似的功能呢?
//父组件
<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;
首先定义 hooks/useURLLoader.tsx
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
然后在 App.jsx 中调用
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
前面useState只执行1次【模拟在生命周期:componentDidMount中执行】,是在第二个参数中,设置为[];
而如果useRef要求只执行1次【模拟在生命周期:componentDidMount中执行】,则设置标识位为false,改变后变为true,进行锁住;
这里实现的是,第一次不执行,待数据更新的时候再去执行
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
Hook规则:
1.只在最顶层使用Hook,不要在循环和条件语句中使用hook
2.只在react函数中调用hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
|
请发表评论