(Day1) React + TypeScript 复习总结

JSX

  1. JSX的本质是JS对象:JSX标签对象 ------> 映射到html标签对象中,React就是用这种方式将JSX渲染到html文档中。
  2. 简单描述一下JSX:
    jsx就是js于xml的一种结合体,理解为js于xml可以一起使用。它在运行的时候会被babel转译 生成 执行React.createElement(‘h1’,null,‘hello’)返回生成的对象结构(React元素),根据这个结构,最后进行页面的渲染(React.render())。
  3. jsx的属性 / 表达式:在大括号中,放一些变量,表达式;属性方面 className , style等。
  4. jsx可以在循环中使用。
import React from 'react';
import ReactDOM from 'react-dom';
let root: HTMLElement | null = document.getElementById('root');

let arr:Array<string> = ['uzi','ming','xiaohu'];
let ReactAry:Array<React.ReactElement> = [];
for( let i = 0;i < arr.length ; i++ ){
    ReactAry.push(<li key={i}>{arr[i]}</li>);
}

ReactDOM.render(ReactAry,root); 

React元素的更新

React元素被创建之后就不可变了,要更新的话只能创建新的元素,重新渲染,并且只会更新必要的部分(DOMdiff 前后比较)。

import React from 'react';
import ReactDOM from 'react-dom'
let root: HTMLElement | null = document.getElementById('root');

let fn = function(){
    let ele: React.ReactElement = (
        <div>
            <div>更新时间:</div>
            <div>{new Date().toLocaleTimeString()}</div>
        </div>
    )
    ReactDOM.render(ele,root)
}

setInterval(()=>{
    fn()
},1000)

React组件 函数式组件和类组件

注意:我们除了学习基本用法外,还要明白它们之间的优缺点,不同之处。

扩展 1) 纯函数: 没有改变自己输入的值,并且也不改变作用域外的变量。

function add(a: number,b:number){ //纯函数
      return a + b
}
function withdraw(account: {name: string},num:number){ //不是纯函数
      account.name += 'adc';
      global.name = 10
}
withdraw({name:'uzi'},10)
  • React是单向数据流,数据只能从上往下传递,并且props是不能修改的。
  • 函数组件就像是一个纯函数,因为这个函数接收了属性对象,而且是只读的.然后返回一个值,也就是:输入是确定的,输出就是确定的。
  • 不过 react,vue都要剥离class组件的趋势,改用函数值组件 hooks => 类组件有的功能,函数式组件也都有。

扩展 2)函数式组件与类组件的区别
1. 函数式组件的性能比类组件的性能要高。毕竟函数只需要执行返回对应的React元素即可,并且每次运行完成后会销毁,减少了内存的开销,而类组件还需要实例化。
2. 函数式组件 没有 this状态生命周期 ;类组件有this、有生命周期有状态state

函数组件的渲染 类组件的渲染
1. 收集props对象;
2.把属性对象传入函数并返回React元素;
3.把React元素渲染到页面上。
1. 也是收集props对象;
2. 实例化对应类的实例;
3. 调用实例的render方法,获得返回的React元素;
4. 把返回的元素渲染到界面上就行。

复合式组件

和普通的复合写法一样,就是注意一下 类型 即可。

import React from 'react';
import ReactDOM from 'react-dom'

let root: HTMLElement | null = document.getElementById('root');
let style: React.CSSProperties = { // 规范css样式的类,乱写的话会报错。
    border: '1px solid red',
    padding:5,
}
interface UpData { // 在定义的时候要使用泛型(接口),使用的时候要指定具体props中值的类型.(调用的是时候要使用真实的接口类型.)
    name1: string;
    name2: string
}
interface RngData {
    uzi: string
}
interface IgData {
    theshy: string
}

class RNG extends React.Component<RngData> { //  这里的泛型 修饰的是 this.props (传递过来的参数)
    render(): React.ReactElement{
        return (
            <div>
                我是RNG电子竞技俱乐部的{this.props.uzi}
            </div>
        )
    }
}

class IG extends React.Component<IgData> {
    render(): React.ReactElement{
        return (
            <div>
                我是IG电子竞技俱乐部的{this.props.theshy}
            </div>
        )
    }
}

class LPL extends React.Component<UpData>{
    render(): React.ReactElement {
    	// this.props.headString = '12' // props是不可修改的
        let {name1,name2} = this.props
        return (
            <div style={style}>
                <RNG uzi={name1}/>
                <IG theshy={name2}/>
            </div>
        )
    }
}

let data: UpData = {
    name1: 'uzi',
    name2: 'TheShy'
}
ReactDOM.render(<LPL {...data} />,root)

如何定义默认属性(结合类型检查)

props 设置默认属性: static defaultProps = {…} ;
类型检查:static propTypes = {…} 或者 类.propTypes = {…}

import React from 'react';
import ReactDom from 'react-dom';
import PropTypes from 'prop-types'
let root: HTMLElement | null = document.getElementById('root');

interface DataProps {
    name?: string;
    gender?: 'male' | 'female';
    hobby?: Array<string>;
    age?: number;
    position?: { x: number, y: number };
    [propName: string]: any // extends Record<string,any>   29行报错解决方案
}

class Animal extends React.Component<DataProps> {
    static defaultProps:DataProps  = { // static defaultProps = {} 给props添加默认属性。
        name: 'jacklove'
    }
    // static propTypes:Record<string,any>
    static propTypes = { // 对props中的属性进行类型检查 <=> Animal.propTypes = {...}
        name: PropTypes.string.isRequired,
        gender: PropTypes.oneOf(['male','female']).isRequired,
        hobby: PropTypes.arrayOf(PropTypes.string), // 存字符串类型的数组
        position: PropTypes.shape({
            x: PropTypes.number,
            y: PropTypes.number
        }),
        // 自定义校验属性 校验年龄age必须大于0并且小于100岁
        age(props: DataProps, propName: string, componentName: string){
            let age = props[propName]; //29行 因为根本不知道 propName里面是值,所以就要求PersonProps类型要支持索引属性。
            if(age < 0 || age >100){
                return new Error(`Invalid Prop ${propName} supplied to ${componentName}`);
            }
            return null;
        }
    }
    render() {
        let { name,gender,hobby,age,position } = this.props
        return (
            <div>
                <ul>
                    <li>name: {name}</li>
                    <li>gender: {gender}</li>
                    <li>hobby: {hobby}</li>
                    <li>age: {age}</li>
                    <li>position: {position!.toString()}</li>
                </ul>
            </div>
        )
    }
}

let dataProps: DataProps = {
    gender: 'male',
    hobby: ['uzi', 'xiaohu'],
    age: 29,
    position: {
        x: 789,
        y: 9
    }
}

ReactDom.render(<Animal {...dataProps} />, root);

原理的基本实现

createElement 创建虚拟DOM

众所周知,React中标签元素会在Bable中进行转移,生成执行函数React.createElement(“h1”, null, “Hello”); 的结果。

// 页面中
<h1 className='rng' style={{color: 'red',fontSize: '25px' }}>Hello</h1>
 				完全等价于 
React.createElement("h1", {
  className: "rng",
  style: {
    color: 'red',
    fontSize: '25px'
  }
}, "Hello");
// React.createElement的实现
// 思路:就是往对象中添加各个需要的属性,作为React元素返回。

// createElement.js
export class Component<P = any> { // 类组件类型(类也是对象,所以也可以作为接口使用)
    isComponent = true; // 类组件的标志
    public props: P;
    static isComponent = true;
    constructor(props: P){
        this.props = props
    }
}

export interface FunctionConstructor { // 函数组件类型
    // (props) => HTMLElement
}

export interface ReactElement {
    type: string | FunctionConstructor | Component,
    props: any
};

function createElement(type: string | FunctionConstructor | Component<any>,config: Record<string,any>,...children:Array<any>): ReactElement {
    // type的类型有 字符串 、 函数(组件)、类(组件)
    let props: Record<string,any> = {}
    for (const propName in config) {
        props[propName] = config[propName]
    };
    props.children = children;
    let element: ReactElement = {type,props};
    return element
}
export default createElement;

// index.js
let ele = createElement("h1", {
    className: "rng",
    style: {
      color: 'red',
      fontSize: '25px'
    }
}, "Hello")

console.log(ele)  // {type: "h1", props: {…}}

render渲染

// element = {type:'h1',props:{className,style,children}}
// 思路: 根据第一个参数传递的类型不同,分布进行处理,根据生成type生成对应的标签。再对标签进行属性的添加,最后渲染到页面。

// render.js
function render(element: ReactElement,container: HTMLElement): any{
    if( typeof element === 'string'){
        return container.appendChild( document.createTextNode(element) )
    };
    let type = element.type,
        props = element.props;
    let domElement: HTMLElement;
    if( (type as Component ).isComponent ){ // 传入的是类
        element = new (type as any)(props).render();
        type = element.type;
        props = element.props;
        domElement = document.createElement(type as string);
    }else if( typeof type === 'function' ){
        element = type();
        type = element.type;
        props = element.props;
        domElement = document.createElement(type as string);
    }else {
        domElement = document.createElement(type as string);
    }

    for (const propName in props) {
        if(propName === 'className'){
            domElement[propName] = props[propName]
        }else if(propName === 'style'){
            let styleObject: CSSStyleDeclaration = props[propName];
            for (const attr in styleObject) {
                domElement.style[attr] = styleObject[attr];
            }
        }else if( propName === 'children' ){
            props[propName].forEach( (child: any) => {
                render(child,domElement);
            } )
        }else{
            domElement.setAttribute(propName,props[propName]);
        }
    }
    container.appendChild(domElement)
}
export default render

// index.js
let ele = createElement("h1", {
    className: "rng",
    style: {
      color: 'red',
      fontSize: '25px'
    } 
  }, "Hello")

render(ele, root!);

函数组件的实现

interface Props {
    title: string
}
let Dog: FunctionConstructor = (props: Props) => {
	// return <h1 className='rng' style={{color: 'red',fontSize: '25px' }}>Hello</h1>
	return createElement("h1", {
	    className: "rng",
	    style: {
	        color: 'red',
	        fontSize: '25px'
	    }
	}, "Hello")
}
let element = createElement(Dog,{title: '标题'})
 render(element,root as HTMLElement)

类组件的实现

// 类组件
interface Props {
    title: string
}
class Dog extends Component<Props> {
    constructor(data: any) {
        super(data)
    }
    render() {
        return createElement('h1', { className: 'ok', style }, 'uzi', 'xiaohu')
    }
}
let element = createElement(Dog , { title: '标题' })
render(element,root as HTMLElement);

状态

状态的用法

. 状态的接受:class Axx extends React.Component <Props,State>
Props,State 分别为 props参数 与 state 状态的 类型接口。
扩展: setInterval 定时器类型的设定 ----> timerID: NodeJS.Timer | null = null

// 状态的用法

// setState 可以看收藏的文章。
import React from 'react';
import ReactDOM from 'react-dom'
let root:HTMLElement | null = document.getElementById('root')

interface Props {

}

interface State {
    number: number
}

class Animal extends React.Component<Props,State> { // 两个状态
    // timerID: number|null = null // timerID: number|null = null 报错
    public timerID: NodeJS.Timer | null = null;
    constructor(props: Props){
        super(props);
        this.state = { // 当前状态
            number: 0
        }
    }
    componentDidMount(){ // 组件挂载完成后,会执行此生命周期函数
        this.timerID = setInterval(()=>{
            this.setState({ // 设置新的状态,设置完新的状态后会引起界面的更新。
                number: this.state.number + 1
            })
        },1000)
        // clearInterval(this.timerID) // 如果参数出现了问题,需要强行转为number类型。
    }
    componentWillMount(){
        clearInterval(Number(this.timerID)) // this.timerID!
    }
    render(){
        return (
            <div>
                {this.state.number}
            </div>
        )
    }
}

ReactDOM.render(<Animal/>,root as HTMLElement);

setTimeout 异步 更新状态

  1. 在jsx中函数执行的三种写法,以及最优写法
  2. setState的更新的方式
// setTimeout更新状态

import React from 'react';
import ReactDOM from 'react-dom'
let root:HTMLElement | null = document.getElementById('root')

interface Props {

}

interface State {
    number: number
}

class Animal extends React.Component<Props,State> { // 两个状态
    state = {number: 0};
    // 箭头函数可以保证函数中的this永远指向类的实例。
    // setState的更新可能是异步的,多个setState可能会被合并为一个。
    handerClick = (ev: React.MouseEvent) => {
        // 在事件处理函数中调用setState的时候,this.state的状态没有真正改变。(setState是异步的)
        // 如果我想从上一个状态计算一个状态的话,需要传递一个函数而非对象。
        /*  
            // 传递函数式 => 不会被压缩 
            this.setState((state) => ({ // 函数传递的第一个参数就是‘旧的’state
                number: state.number + 1
            }))
            this.setState((state) => ({
                number: state.number + 1
            }))
            this.setState((state) => ({
                number: state.number + 1
            }))
        */
       this.setState({number: this.state.number + 1})
       console.log(this.state.number) // 因为this.state不会立即改变 =》 0
       this.setState({number: this.state.number + 1})
       console.log(this.state.number) // 同上 =》 0
       /*
        压缩 后
            console.log(this.state.number);
            console.log(this.state.number);
            this.setState({number: this.state.number + 1})
       */
            
       // setTimeout里的代码比较特殊,不会走批量更新,会立即执行跟新。
       // =》 setState 在原生事件和setTimeout 中都是同步的
       // 上面的执行完为 state => 1
       setTimeout(()=>{
        this.setState({number: this.state.number + 1})
        console.log(this.state.number) // 2
        this.setState({number: this.state.number + 1})
        console.log(this.state.number) // 3
       },0)
        
    }
    render(){
        return (
            <div>
                <p>{this.state.number}</p>
                <button onClick={this.handerClick}>+</button>
            </div>
        )
    }
}

ReactDOM.render(<Animal/>,root as HTMLElement);

状态更新可能会合并

import React from 'react';
import ReactDOM from 'react-dom'
let root:HTMLElement | null = document.getElementById('root')

interface Props {

}

interface State {
    number: number;
    name: string;
    age?: number;
}

class Animal extends React.Component<Props,State> { 
    state = {number: 0,name: 'uzi'};
    handerClick = (ev: React.MouseEvent) => {
        // 这里的状态可能只是一部分状态,这种部分状态会被合并到总的状态上去。
        // 新的会覆盖旧的,原来没有的 会追加上去。
        this.setState({number: this.state.number + 1,age: 90})
    }
    render(){
        return (
            <div>
                <p>{this.state.name}:{this.state.number}</p>
                <button onClick={this.handerClick}>+</button>
            </div>
        )
    }
}

ReactDOM.render(<Animal/>,root as HTMLElement);

单向数据流、状态提升

// 单向数据流、状态提升

import React from 'react';
import ReactDOM from 'react-dom'
let root:HTMLElement | null = document.getElementById('root')

interface Props {
    club: string,
}

interface State {
    number: number;
    name: string;
}

class Animal extends React.Component<Props,State> { 
    state = {number: 0,name: 'uzi'};
    // HTMLButtonElement 是 button 的事件类型。(当前事件源DOM类型,使得ev更加具体);
    // MouseEvent 是浏览器DOM事件类型,与 React.MouseEvent 是不同的。
    handerClick = (ev: React.MouseEvent<HTMLButtonElement,MouseEvent>) => { 
        // console.log(ev.target) // 事件源
        console.log(ev.currentTarget.id); //获取事件源属性 要使用 ev.currentTarget 来获取。
        this.setState({number: this.state.number + 1});
    }
    add = () => {
        this.setState({number: this.state.number + 1})
    }
    render(){
        return (
            <div>
                <p>{this.state.name}:{this.state.number}</p>
                <button id='jklove' onClick={this.handerClick}>+</button>
                <Dog {...this.props} {...this.state} add={this.add}/>
            </div>
        )
    }
}
// 父组件传递下来的值,都会记录在子类的props中。
// 子类不可以修改父组件传递下来的值,是只读的。
// 虽然子类不能动父类的属性和状态,但是 子类可以执行父类传递过来的方法 => 从而修改父类的属性 => 状态提升
// 状态提升:两个组件要共享数据,将数据放在他们共有的最近的父类上
class Dog extends React.Component<Props & State & {add: any} >{ // 将State中的状态合并到Props中。
    render(){
        return (
            <div>
                {this.props.club} : {this.props.name} : {this.props.number}
                <button onClick={this.props.add}>++</button>
            </div>
        )
    }
}

// 函数组件
type SubConterProps = Props & State
// React.SFC === React.FunctionComponent 就是指函数组件。
// 将React.SFC<PropsType>的类型分配给变量,以便将其理解为React无状态功能组件。
let SubConter: React.SFC<SubConterProps> = function(props: SubConterProps){
    return (
        <div>
            {props.club} : {props.name} : {props.number}
        </div>
    )
}

let props:Props & State = {
    club: 'RNG',
    name: 'uzi',
    number: 12
}
ReactDOM.render(<Animal club='ig'/>,root as HTMLElement);
// ReactDOM.render(<SubConter {...props} />,root as HTMLElement);

新生命周期函数

少了创建时的 ComponentWillMount;
多了更新时的 getDerivedStateFromProps、getSnapshotBeforeUpdata;

// getDerivedStateFromProps 例子
import React from 'react';
import ReactDOM from 'react-dom'
let root:HTMLElement | null = document.getElementById('root')

interface Props {

}

interface State {
    number: number
}

class Counter extends React.Component<Props,State> {
    constructor(props: Props){
        super(props);
        this.state = {
            number: 0
        }
    }
    handerClick = () => {
        this.setState({number: this.state.number + 1})
    }
    render(){
        return (
            <div>
                <p>{this.state.number}</p>
                <button onClick={this.handerClick}>+</button>
                <hr/>
                <ChildCounter n={this.state.number}/>
            </div>
        )
    }
}

interface ChildCounterProps {
    n: number
}
interface ChildCounterState {
    num: number
}
class ChildCounter extends React.Component<ChildCounterProps,ChildCounterState> {
    state = {num: 0};
    // 把属性映射为新的状态,它返回的就是新的状态对象。
    // componentWillReceiveProps 已经废弃,取而代之的是 getDerivedStateFromProps; 静态要比非静态的性能要高一些。
    static getDerivedStateFromProps(nextProps: ChildCounterProps,prevState: ChildCounterState){
        const {n} = nextProps;
        if( n%2 === 0 ){
            return {num: n*2};
        }else{
            return {num: n*3}
        }
    }
    shouldComponentUpdate(): boolean{
        return this.state.num % 2 === 0 // 如果 值为true 则进行更新。
    }
    // 补充:
    // render是一个函数,它把react组件转成虚拟DOM元素;
    // react-dom负责把虚拟DOM变成真实DOM.
    render(){
        return (
            <div>
                {this.state.num}
            </div>
        )
    }
}

ReactDOM.render(<Counter />,root as HTMLElement);
// getSnapshotBeforeUpdata 例子
// 新生命周期 getSnapshotBeforeUpdate 实现 页面固定显示位置
import React,{RefObject} from 'react';
import ReactDOM from 'react-dom'
let root:HTMLElement | null = document.getElementById('root')

interface ScrollProps {
    
}

interface ScrollState {
    messages: Array<string>
}

class ScrollList extends React.Component<ScrollProps,ScrollState> {
    timer: NodeJS.Timeout | null = null
    wrapper: RefObject<HTMLDivElement>; // 真实dom元素的类型
    constructor(props: ScrollProps){
        super(props);
        this.state = {
            messages: []
        }
        this.wrapper = React.createRef<HTMLDivElement>(); // ???????
    }
    componentDidMount(){
        this.timer = setInterval(()=>{
            this.setState({
                messages: [`messages-${this.state.messages.length}`,...this.state.messages]
            })
        },1000)
    }
    componentWillUnmount(){
        clearInterval(Number(this.timer));
    }
    // getSnapshotBeforeUpdate 可以获取 老的 DOM元素的信息,将返回值,以参数的形式传递到componentDidUpdate的第三个参数中。
    getSnapshotBeforeUpdate(): number{
        // this.wrapper.current < = > HTMLDivElement
        return this.wrapper.current!.scrollHeight // 内容高度
    }
    componentDidUpdate(prevProps: ScrollProps, prevState: ScrollState , prevScrollHeight: number){
        this.wrapper.current!.scrollTop = this.wrapper.current!.scrollTop + (this.wrapper.current!.scrollHeight - prevScrollHeight);
    }
    render(){
        let style = {
            height: 100,
            width: 200,
            border: '1px solid red',
            overflow: 'auto'
        }
        return (
            <div style={style} ref={this.wrapper}>
                {
                    this.state.messages.map((message: string,index: number) => (<div key={index}>{message}</div>) )
                }
            </div>
        )
    }
}

ReactDOM.render(<ScrollList />,root as HTMLElement);

猜你喜欢

转载自blog.csdn.net/qq_42387542/article/details/107442614