1.入门

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello</title>
</head>

<body>
  <div id="test"></div>

  <script src="../react/react.development.js"></script>
  <script src="../react/react-dom.development.js"></script>
  <script src="../react/babel.min.js"></script>

  <script type="text/babel">

    // 1.入门
    {
      // (1).创建虚拟DOM
      // const VDOM = <h1>Hello</h1>
      // (2).渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉
      // ReactDOM.render(VDOM,document.getElementById("test"));
    }

    // 2.JSX语法
    {
      // const myId = "title";
      // const myData = "Hello";
      // (1).创建虚拟DOM
      // const VDOM = (
      //   <h1 id={myId.toLocaleUpperCase()}>
      //     <span className="active" style={{ fontSize: '50px' }}>{myData}</span>
      //   </h1>
      // )
      // (2).渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉
      // ReactDOM.render(VDOM, document.getElementById("test"));
    }

    // 3.两种创建虚拟DOM的方式
    {
      // (1).JSX创建虚拟DOM
      // const VDOM = (
      //   <h1 id="title">
      //     <span className="active" style={{ fontSize: '50px' }}>Hello</span>
      //   </h1>
      // )
      // (2).JS创建虚拟DOM
      // const vDOM = React.createElement('h1',{id:"title"}, React.createElement('span', {}, 'Hello'));

      // 第一种方式会被babel转为第二种方式(devtools元素面板可验证)
      // 所以第一种方式是第二种的语法糖,由react提供
    }

    // 4.两种组件
    {
      // (1).函数式组件:适于简单组件 
      // function Welcome(props) {
      //   return <h1>Hello, {props.name}</h1>;
      // }
      // ReactDOM.render(<Welcome name="A"/>, document.getElementById("test"));

      // (2). 类式组件:适于复杂组件
      // class Welcome extends React.Component {
      //   render() {
      //     return <h1>Hello, {this.props.name}</h1>
      //   }
      // }
      // ReactDOM.render(<Welcome name="B"/>, document.getElementById("test"));
    }

    // 5.组件嵌套
    {
      // function GetLi(props) {
      //   return <li>{props.value}</li>
      // }

      // class GetUl extends React.Component {
      //   render() {
      //     return (
      //       <ul>
      //         {
      //           this.props.arr.map((item, index) =>
      //             <GetLi value={item} key={index} />
      //           )
      //         }
      //       </ul>
      //     )
      //   }
      // }

      // ReactDOM.render(<GetUl arr={[1, 2, 3, 4]} />, document.getElementById("test"));
    }
  </script>
</body>

</html>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.

2. 三大属性

(1).state

{
  class MyComp extends React.Component {
    constructor(props) {
      super(props);
      this.state = { status: false };
      //这里的this必然是实例对象

      this.changeStatus = this.changeStatus.bind(this);
      //上面这句话,右边的changeStatus是原型对象上的,左边的changeStatus是实例对象上的
    }
    //类中定义的方法都在原型对象上
    render() {
      console.log(this);//通过Mycomp的实例调用render方法时,这里的this是该实例对象
      return <h1 onClick={this.changeStatus}>状态:{this.state.status ? '正常' : '异常'}</h1>
    }
    //类中定义的方法都在原型对象上
    changeStatus() {
      console.log(this);//通过Mycomp的实例调用render方法时,这里的this是该实例对象
      //但是当h1标签被点击,从而调用changeStatus方法时,实际调的是它的一个克隆体(全局作用域内)
      //此时this理应为window对象,但是会开启局部严格模式,则this最终为undefined

      //拿不到正确的this就无法修改status,那咋办?
      //有些方法(如call、apply、bind)是可以修改this的
      //比如 this.changeStatus.bind(this)会返回一个新的方法并为新方法制定了this
      //那么我们希望onClick的回调,是这个新方法
      //目前onClick的回调,是原型对象的老方法
      //而之所以是原型对象上的方法,是因为实例对象上没有找到名为changeStatus的方法,如果找到了,那么优先用这个
      //那么思路就清晰了:给实例对象加个名为changeStatus的属性,属性值是this.changeStatus.bind(this)的返回值
      //加属性要在构造器里加

      // this.state.status = true;
      // console.log(this.state.status);//true
      //直接更改,没有“响应式”的效果

      //在vue中也是这样,state放在data里,如果只改state的属性,则不触发视图的更改(错×,vue深度监听了对象)
      // this.state = { status: !this.state.status };
      // console.log(this.state);

      //在react中,上面这么写也不行,得借助框架提供的API
      this.setState({ status: !this.state.status });
      // 如果只是修改state的部分属性,则不会影响其他的属性,这个只是合并并不是覆盖。上面我自己的写法是覆盖
    }
  }
  ReactDOM.render(<MyComp />, document.getElementById("test"));
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

state 简化写法:

class MyComp extends React.Component {
  state = { isHot: false };
  render() {
    return <h1 onClick={this.changeState}>今天天气{this.state.isHot ? '炎热' : '凉爽'}</h1>
  }
  //箭头函数没有自己的this,会向外找
  changeState = () => {
    this.setState({ isHot: !this.state.isHot });//异步操作
  }
}

ReactDOM.render(<MyComp />, document.getElementById("test"));

/**
 * React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。
 * 大部分开发中用到的都是React封装的事件,比如onChange、onClick等,setState都是异步处理的。
**/
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

React调用setState方法时,会有同步与异步之分。

如果是同步更新,每一个setState都会执行,每次执行会调用一次render(多次渲染)

如果是异步更新,多个setState会仅执行最后一个,这样仅调用一次render(一次渲染)

class MyComp extends React.Component {
  state = { isHot: false, cnt: 0 };
  render() {
    return <h1 onClick={this.changeState}>今天天气{this.state.isHot ? '炎热' : '凉爽'},数量:{this.state.cnt}</h1>
  }
  changeState = () => {
    this.setState({ cnt: this.state.cnt + 1 });
    this.setState({ cnt: this.state.cnt + 3 });
    this.setState({ cnt: this.state.cnt + 5 });
  }
}

ReactDOM.render(<MyComp />, document.getElementById("test"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

这里 cnt 最终为5,也就是仅执行了最后一个setState。

自定义方法的传参

class MyComp extends React.Component {
  state = { isHot: false, cnt: 0, num: 5 };
  render() {
    // return <h1 onClick={(e) => this.changeState(this.state.num, e)}>今天天气{this.state.isHot ? '炎热' : '凉爽'},数量:{this.state.cnt}</h1>
    return <h1 onClick={this.changeState.bind(this, this.state.num)}>今天天气{this.state.isHot ? '炎热' : '凉爽'},数量:{this.state.cnt}</h1>
  }
  changeState = (n, e) => {
    this.setState({ cnt: this.state.cnt + n });
    console.log(e.target);
  }
}

ReactDOM.render(<MyComp />, document.getElementById("test"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

有①箭头函数②bind两种方式

第二种方式e也能读取到

(2).props

props是只读的(read-only)

给组件传值

class Person extends React.Component {
  render() {
    return (
      <ul>
        <li>name: {this.props.name}</li>
        <li>age: {this.props.age}</li>
        <li>gender: {this.props.gender}</li>
      </ul>
    );
  }
}

ReactDOM.render(<Person name="Joy" age="25" gender="male"/>, document.getElementById("test"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

给组件传一个对象

class Person extends React.Component {
  render() {
    return (
      <ul>
        <li>name: {this.props.name}</li>
        <li>age: {this.props.age}</li>
        <li>gender: {this.props.gender}</li>
      </ul>
    );
  }
}

ReactDOM.render(<Person name="Joy" age="25" gender="male"/>, document.getElementById("test"));
const p = { name: "Mike", age: 28, gender: "male" };
ReactDOM.render(<Person {...p}/>, document.getElementById("test2"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

原本在JS中,剩余运算符不能用于展开一个对象,这里能这么写,是因为react+babel允许在自定义组件中写这种语法


纯JS中这么写的话,只能是复制一个对象:

const p1 = {name:"张三",age:"18",sex:"女"};
const p2 = {...p1};
p1.name = "sss";
console.log(p2);  //{name:"张三",age:"18",sex:"女"}

//复制时修改属性
const p1 = {name:"张三",age:"18",sex:"女"};
const p2 = {...p1,name : "111",hua:"ss"};
p1.name = "sss";
console.log(p2);  //{name: "111", age: "18", sex: "女",hua:"ss"}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.


限制:

  • 类型检查:propTypes
  • 是否必填:propTypes
  • 设默认值:defaultProps
//vue中的写法
export default {
  name: 'MyCount',
  // props: ['count', 'state'],
  props: {
    count: {
      type: Number,
      required: true,
      default: 100
    },
    state: Boolean,
    info: [String, Number]
  },
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
//react
class Person extends React.Component {
  render() {
    return (
      <ul>
        <li>name: {this.props.name}</li>
        <li>age: {this.props.age}</li>
        <li>gender: {this.props.gender()}</li>
      </ul>
    );
  }

  static propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.string,
    gender: PropTypes.func
  }

  static defaultProps = {
    name: "Anonymity",
    age: "18",
  }
}

const getGender = () => { return "male"; }

ReactDOM.render(<Person gender={getGender}/>, document.getElementById("test"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.


以上是在类式组件中使用props,下面是在函数式组件中使用props:

function Animal(props) {
  return (
    <ul>
      <li>{props.name}</li>  
      <li>{props.age}</li>  
      <li>{props.gender}</li>  
    </ul>
  )
}

const dog = { name: "Mike", age: 28, gender: "male" };
ReactDOM.render(<Animal {...dog}/>, document.getElementById("test2"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.


(3).refs

①.字符串形式

官方不推荐,说后续版本可能移除这种方式

class Demo extends React.Component {
  render() {
    return (
      <div>
        <input ref="first" type="text" placeholder="Click button to get the input value"/>
        <button onClick={this.getFirstValue}>Click</button>
        <input onBlur={this.getSecondValue} ref="second" type="text" placeholder="Blur to get the input value"/>
      </div>
    )
  }
  getFirstValue = () => {
    console.log(this.refs.first.value);
  }
  getSecondValue = () => {
    console.log(this.refs.second.value);
  }
}

ReactDOM.render(<Demo/>, document.getElementById("test"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
②.回调形式

将当前DOM元素赋给类自定义属性

class Demo extends React.Component {
  render() {
    return (
      <div>
        <input ref={ element => this.first = element } type="text" placeholder="Click button to get the input value" />
        <button onClick={this.getFirstValue}>Click</button>
        <input ref={element => this.second = element} onBlur={this.getSecondValue} type="text" placeholder="Blur to get the input value"/>
      </div>
    )
  }
  getFirstValue = () => {
    console.log(this.first.value);
  }
  getSecondValue = () => {
    console.log(this.second.value);
  }
}

ReactDOM.render(<Demo />, document.getElementById("test2"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

回调形式有个特点:直接在DOM上写回调时,在重新渲染时,首次的element为null,第二次才是DOM元素。

可以改成从外面传回调。不过即使不这么做也没啥大问题。

class Demo extends React.Component {
  state = { isHot: false }
  getFirstRef = element => {
    this.first = element;
    console.log(element);
  }
  render() {
    return (
      <div>
        <p>Today is {this.state.isHot ? 'hot' : 'cool'}.</p>
        {/*<input ref={ element => {this.first = element; console.log(element)}} type="text" placeholder="Click button to get the input value" />*/}
        <input ref={ this.getFirstRef } type="text" placeholder="Click button to get the input value" />
        <button onClick={this.getFirstValue}>Click</button>
        <input ref={ element => this.second = element } onBlur={this.getSecondValue}  type="text" placeholder="Blur to get the input value" />
      </div>
    )
  }
  getFirstValue = () => {
    console.log(this.first.value);
    this.setState({ isHot: !this.state.isHot })
  }
  getSecondValue = () => {
    console.log(this.second.value);
  }
}

ReactDOM.render(<Demo />, document.getElementById("test1"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
③.API形式

React.createRef()创建React的容器,这个容器是专人专用的,所以每一个ref都需要创建这个。该API会将DOM元素赋值给实例对象的名称为容器名称的属性的current【这个current是固定的】

class Demo extends React.Component {
  first = React.createRef();
  second = React.createRef();
  render() {
    return (
      <div>
        <input ref={ this.first } type="text" placeholder="Click button to get the input value" />
        <button onClick={this.getFirstValue}>Click</button>
        <input ref={ this.second } onBlur={this.getSecondValue}  type="text" placeholder="Blur to get the input value" />
      </div>
    )
  }
  getFirstValue = () => {
    console.log(this.first.current.value);
  }
  getSecondValue = () => {
    console.log(this.second.current.value);
  }
}

ReactDOM.render(<Demo />, document.getElementById("test2"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.


事件替代Ref

官方提示我们不要过度的使用ref,如果发生事件的元素刚好是需要操作的元素,就可以使用事件去替代。

class Demo extends React.Component {
  first = React.createRef();
  render() {
    return (
      <div>
        <input ref={ this.first } type="text" placeholder="Click button to get the input value" />
        <button onClick={this.getFirstValue}>Click</button>
        <input onBlur={event => console.log(event.target.value)}  type="text" placeholder="Blur to get the input value" />
      </div>
    )
  }
  getFirstValue = () => {
    console.log(this.first.current.value);
  }
}

ReactDOM.render(<Demo />, document.getElementById("test3"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

如果发生事件的元素不是需要操作的元素,也有办法

class Demo extends React.Component {
  render() {
    return (
      <div>
        <input onChange={ event => this.setState({ first: event.target.value }) } type="text" placeholder="Click button to get the input value" />
        <button onClick={ () => console.log(this.state.first) }>Click</button>
        <input onBlur={event => console.log(event.target.value)}  type="text" placeholder="Blur to get the input value" />
      </div>
    )
  }
}

ReactDOM.render(<Demo />, document.getElementById("test4"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

3.受控组件与非受控组件

React中,表单元素(如<input>、 <textarea> 和 <select>)分为受控组件与非受控组件,我的理解:

  • 受控组件:类似vue的双向绑定(推荐)(value不仅用户可以设置,还可由开发人员通过脚本设置)
  • 非受控组件:只有 View => Model 的单项流动(value仅由用户设置)

React 学习记录_JS

// 非受控
class Login extends React.Component {
  username = React.createRef();
  password = React.createRef();
  render() {
    return (
      <form action="" onSubmit={this.login}>
        <label htmlFor="username">用户名</label>
        <input type="text" name="username" id="username" ref={ this.username }/>
        <br/>
        <label htmlFor="password">密码</label>
        <input type="password" name="password" id="password" ref={ this.password }/>
        <button>登录</button>
      </form>
    )
  }
  login = (e) => {
    e.preventDefault();
    console.log('username: ', this.username.current.value, 'password: ', this.password.current.value);
  }
}
ReactDOM.render(<Login/>, document.getElementById("test"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
// 受控:即时现场验证
class Login extends React.Component {
  state = { username: '', password: '' }
  render() {
    return (
      <form action="" onSubmit={this.login}>
        <label htmlFor="username">用户名</label>
        <input type="text" name="username" id="username" 
          value={this.state.username} onChange={this.checkUsername} />
        <br />
        <label htmlFor="password">密码</label>
        <input type="password" name="password" id="password" 
          value={this.state.password} onChange={this.checkPassword} />
        <button>登录</button>
      </form>
    )
  }
  checkUsername = (e) => {
    const usernameRegex = /^[a-zA-Z0-9]{0,8}$/;
    const flag = usernameRegex.test(e.target.value);
    flag && (this.setState({ username: e.target.value }))
    setTimeout(() => {
      console.log(flag, this.state.username);
    })
  }
  checkPassword = (e) => {
    //长度在 8 到 20 个字符之间,必须包含至少一个大写字母、一个小写字母和一个数字
    const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{0,20}$/;
    const flag = passwordRegex.test(e.target.value);
    this.setState({ password: e.target.value });
    setTimeout(() => {
      console.log(flag, this.state.password);
    })
  }
  login = (e) => {
    e.preventDefault();
    console.log('username: ', this.username.current.value, 'password: ', this.password.current.value);
  }
}
ReactDOM.render(<Login />, document.getElementById("test2"));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.