【前端丛林】React这样服用,效果更佳(2)

79 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

前言

哈喽大家好,我是Lotzinfly,一位前端小猎人。欢迎大家再次来到前端丛林,在这里你将会遇到各种各样的前端猎物,我希望可以把这些前端猎物统统拿下,嚼碎了服用,并成为自己身上的骨肉。今天是国庆假期第二天,也是我们冒险的第二天,昨天我们对React做了一个开头,让我们对React有了初步的了解,今天我们会继续深入学习React。在这七天假期里,让我们学会React实现弯道超车。你们准备好了吗?那么开始我们的冒险之旅吧!

1.状态(State)

  • 组件的数据来源有两个地方,分别是属性对象和状态对象
  • 属性是父组件传递过来的(默认属性,属性校验)
  • 状态是自己内部的,改变状态唯一的方式就是 setState
  • 属性和状态的变化都会影响视图更新
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
      constructor(props) {
      super(props);
      this.state = {date: new Date()};
    } 
     componentDidMount() {
        this.timerID = setInterval(
          () => this.tick(),
                1000
         );
     }
     componentWillUnmount() {
        clearInterval(this.timerID);
     }
    tick() {
     this.setState({
     date: new Date()
        });
  }
      render() {
         return (
            <div>
               <h1>Hello, world!</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}</h2>
            </div>
            );
          }
      }
ReactDOM.render(
     <Clock />,
     document.getElementById('root')
   );

1.1 不要直接修改 State

构造函数是唯一可以给 this.state 赋值的地方

import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            number: 0
        };
    }
    componentDidMount() {
        this.timerID = setInterval(
            () => {
                //this.setState({number:this.state.number+1});
                this.state.number = this.state.number + 1;
            },
            1000
        );
    }
    componentWillUnmount() {
        clearInterval(this.timerID);
    }
    render() {
        return (
            <div >
                <p> {this.state.number} </p>
            </div>
        );
    }
}
ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);

1.2 State 的更新可能是异步的

  • 出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用
  • 因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态
  • 可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            number: 0
        };
    }
    handleClick = () => {
        //this.setState({number:this.state.number+1});
        //console.log(this.state.number);
        //this.setState({number:this.state.number+1});
        this.setState((state) => (
            { number: state.number + 1 }
        ));
        this.setState((state) => (
            { number: state.number + 1 }
        ));
    }
    render() {
        return (
            <div >
                <p> {this.state.number} </p>
                <button onClick={this.handleClick}>+</button>
            </div>
        );
    }
}
ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);

1.3 State 的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state

import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: '小明',
            number: 0
        };
    }
    handleClick = () => {
        //this.setState({number:this.state.number+1});
        //console.log(this.state.number);
        //this.setState({number:this.state.number+1});
        this.setState((state) => (
            { number: state.number + 1 }
        ));
        this.setState((state) => (
            { number: state.number + 1 }
        ));
    }
    render() {
        return (
            <div >
                <p>{this.state.name}: {this.state.number} </p>
                <button onClick={this.handleClick}>+</button>
            </div>
        );
    }
}
ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);

1.4 数据是向下流动的

  • 不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件
  • 这就是为什么称 state 为局部的或是封装的的原因,除了拥有并设置了它的组件,其他组件都无法访问
  • 任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件
  • 如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: '小明',
            number: 0
        };
    }
    handleClick = () => {
        this.setState((state) => (
            { number: state.number + 1 }
        ));
    }
    render() {
        return (
            <div style={{ border: '1px solid red' }}>
                <p>{this.state.name}: {this.state.number} </p>
                <button onClick={this.handleClick}>+</button>
                <SubCounter number={this.state.number} />
            </div>
        );
    }
}
class SubCounter extends React.Component {
    render() {
        return <div style={{ border: '1px solid blue' }}>子计数器:{this.props.number}</div>;
    }
}
ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);

2. 事件处理

2.1 理解事件

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
  • 你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault
import React from 'react';
import ReactDOM from 'react-dom';
class Link extends React.Component {
    handleClick(e) {
        e.preventDefault();
        console.log('The link was clicked.');
    }
    render() {
        return (
            <a href="http://www.baidu.com" onClick={this.handleClick}>
                Click me
            </a>
        );
    }
}
ReactDOM.render(
    <Link />,
    document.getElementById('root')
);

2.2 this

必须谨慎对待 JSX 回调函数中的 this,可以使用:

  • 公共属性(箭头函数)
  • 匿名函数
  • bind进行绑定
class LoggingButton extends React.Component {
    handleClick() {
        console.log('this is:', this);
    }
    handleClick1 = () => {
        console.log('this is:', this);
    }
    render() {
        //onClick={this.handleClick.bind(this)
        return (
            <button onClick={(event) => this.handleClick(event)}>
                Click me
            </button>
        );
    }
}

2.3 向事件处理程序传递参数

  • 匿名函数
  • bind
class LoggingButton extends React.Component {
    handleClick1 = (id, event) => {
        console.log('id:', id);
    }
    render() {
        return (
            <>
                <button onClick={(event) => this.handleClick('1', event)}>
                    Click me
                </button>
                <button onClick={this.handleClick.bind(this, '1')}>
                    Click me
                </button>
            </>
        );
    }
}

2.4 Ref

  • Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素
  • 在 React 渲染生命周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个 defaultValue 属性,而不是value

2.4.1 ref的值是一个字符串

class Sum extends React.Component {
    handleAdd = (event) => {
        let a = this.refs.a.value;
        let b = this.refs.b.value;
        this.refs.c.value = a + b;
    }
    render() {
        return (
            <>
                <input ref="a" />+<input ref="b" />
                <button onClick={this.handleAdd}>=</button><input ref="c" />
            </>
        );
    }
}

2.4.2 ref的值是一个函数

class Sum extends React.Component {
    handleAdd = (event) => {
        let a = this.a.value;
        let b = this.b.value;
        this.result.value = a + b;
    }
    render() {
        return (
            <>
                <input ref={ref => this.a = ref} />+<input ref={ref => this.b = ref} />
                <button onClick={this.handleAdd}>=</button><input ref={ref => this.result = ref} />
            </>
        );
    }
}

2.4.3 为 DOM 元素添加 ref

  • 可以使用 ref 去存储 DOM 节点的引用
  • 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM元素作为其 current 属性
class Sum extends React.Component {
    constructor(props) {
        super(props);
        this.a = React.createRef();
        this.b = React.createRef();
        this.result = React.createRef();
    }
    handleAdd = () => {
        let a = this.a.current.value;
        let b = this.b.current.value;
        this.result.current.value = a + b;
    }
    render() {
        return (
            <>
                <input ref={this.a} />+<input ref={this.b} />
                <button onClick={this.handleAdd}>=</button><input ref={this.result} />
            </>
        );
    }
}

2.4.4 为 class 组件添加 Ref

当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性

class Form extends React.Component {
    constructor(props) {
        super(props);
        this.input = React.createRef();
    }
    getFocus = () => {
        this.input.current.getFocus();
    }
    render() {
        return (
            <>
                <TextInput ref={this.input} />
                <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}
class TextInput extends React.Component {
    constructor(props) {
        super(props);
        this.input = React.createRef();
    }
    getFocus = () => {
        this.input.current.focus();
    }
    render() {
        return <input ref={this.input} />
    }
}

2.4.5 Ref转发

  • 不能在函数组件上使用 ref 属性,因为他们没有实例
  • Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧
  • Ref 转发允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件
class Form extends React.Component {
    constructor(props) {
        super(props);
        this.input = React.createRef();
    }
    getFocus = () => {
        this.input.current.getFocus();
    }
    render() {
        return (
            <>
                <TextInput ref={this.input} />
                <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}//Function components cannot be given refs. Attempts to access this ref will fail. Did you mea n to use React.forwardRef()?
function TextInput() {
    return <input />
}

使用forwardRef

class Form extends React.Component {
    constructor(props) {
        super(props);
        this.input = React.createRef();
    }
    getFocus = () => {
        this.input.current.focus();
    }
    render() {
        return (
            <>
                <TextInput ref={this.input} />
                <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}
const TextInput = React.forwardRef((props, ref) => (
    <input ref={ref} />
));

实现

function createRef() {
    return {
        current: null
    }
}
class Form extends React.Component {
    constructor(props) {
        super(props);
        this.input = createRef();
    }
    getFocus = () => {
        this.input.current.focus();
    }
    render() {
        return (
            <>
                <TextInput myref={this.input} />
                <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}
function forwardRef(funcComponent) {
    return function (props) {
        let ref = props.myref;
        return funcComponent(props, ref);
    }
}
const TextInput = forwardRef((props, ref) => (
    <input ref={ref} />
));

结尾

好啦,这期的前端丛林大冒险先到这里啦!这期我们介绍了React的组件状态(State)和事件处理,大家一定要好好理解,把它搞定,啃下来嚼烂嚼透。希望大家可以好好品尝并消化,迅速升级,接下来我们才更好地过五关斩六将!好啦,我们下期再见。拜拜!