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元素被创建之后就不可变了,要更新的话只能创建新的元素,重新渲染,并且只会更新必要的部分(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)
注意:我们除了学习基本用法外,还要明白它们之间的优缺点,不同之处。
扩展 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)
扩展 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 29行报错解决方案
}
class Animal extends React.Component<DataProps> {
static defaultProps:DataProps = { // static defaultProps = {} 给props添加默认属性。
name: 'jacklove'
}
// static propTypes:Record
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);
众所周知,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: {…}}
// 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 Hello
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 状态的 类型接口。
扩展: 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更新状态
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的类型分配给变量,以便将其理解为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( ,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);