react之基本语法

2022-02-20
10 min read
Featured Image

Creating a Toolchain from Scratch

A JavaScript build toolchain typically consists of:

  • A package manager, such as Yarn or npm. It lets you take advantage of a vast ecosystem of third-party packages, and easily install or update them.
  • A bundler, such as webpack or Parcel. It lets you write modular code and bundle it together into small packages to optimize load time.
  • A compiler such as Babel. It lets you write modern JavaScript code that still works in older browsers.

If you prefer to set up your own JavaScript toolchain from scratch, check out this guide that re-creates some of the Create React App functionality.

学习联想

  • jsx 中 class 需要写成 className, js 里那个 className 是什么来着
    • 是在事件函数里, 判断 target的时候 if (target.className === 'new-todo' && code === 13)
    • 自定义属性是 data-miss = "you"
      • if (target.dataset.miss === you)

JSX 语法

基本规则

  1. 定义虚拟 DOM 时, 不要写引号, 直接在小括号里面写 html.
  2. 标签中混用 js 表达式 需要用{}.
  3. 样式类型为 className.
  4. 内联样式, 格式为 style = {{key:value,key1:value}}
  5. 只有一个根标签.
  6. 标签必须闭合.
  7. 标签首字母.
    1. 若小写字母, 浏览器尝试转 html, 若没有同名的标签则报错.
    2. 若大写字母, react 就去渲染对应的组件 (component), 若组件没有定义, 就报错.

js 表达式

  • js 表达式:

    • 表达式会产生一个值, 然后放在一个需要值得地方, 以下表达式, 均可赋值于变量, 也就是在左边可以加变量和等于号

      • a
      • a+b
      • foo(1)
      • arr.map(): 遍历数组中每一个元素, 函数处理后, 返回一个新数组.
      • function foo() {}
  • js 语句:

    • if
    • for 循环
    • switch

组件

函数式组件(简单组件)

  • 函数首字母大写
  • 传递函数的时候元素用自闭和标签
<script type="text/babel">
      function Demo(){
    		console.log(this) // 输出为 undefined, 原因是 babel 翻译后开启严格模式, 禁止 this 指向 window
         return <h2> 我使用函数定义的组件 </h2>
    		
      }
      ReactDOM.render(<Demo/>,document.getElementById('test'))
 </script>
  • 执行了 ReactDOM.render 后,
    1. React 解析组件标签, 找到了 Demo 组件.
    2. 发现组件是函数定义的, 随后调用该函数, 将返回的虚拟 DOM 转为真实 DOM, 随后呈现在页面中.

类式组件(复杂组件)

<script type="text/babel">
      class Demo extends React.Component {
         render() {
           console.log(this) //this 指向 Demo 的实例对象
            return <h2>Welcome</h2>
         }
      }
      ReactDOM.render(<Demo/>,document.getElementById('test'))
 </script>
  • 执行了 ReactDOM.render 后,
    1. React 解析组件标签, 找到了 Demo 组件.
    2. 发现组件是类定义的, 随后new 一个该类的实例, 通过该实例调用原型上的 render 方法
    3. 将 render 返回的虚拟 DOM 转为真是 DOM, 随后呈现在页面中.

类式组件的三大属性

1. 类式组件实例三大属性之 state

  • 通过构造器传需要使用的 state
  • 感觉就像之前通过自定义标签存放的一些数据, 都通过对象形式整合到 state 中去了
<script type="text/babel">
      class Weather extends React.Component { //需要继承
         constructor(props) {
            super(props);
            //初始化状态
            this.state = {isHot:false}
         }
         render() {
            console.log(this);
            //读取状态
            const {isHot} = this.state;
            return <h2> 今天天气很 {isHot ? '炎热' : '凉爽'}</h2>
         }
      }
      ReactDOM.render(<Weather/>,document.getElementById('test'))
   </script>

组件内原型上自定义函数的 this 指向

  • 类中是默认局部开启严格模式
class Person() {
  constructor() {
  }
  study(){
    console.log(this)
  }
}
const p1 = new Person();
p1.study(); //输出指向 Person 实例
const fn = p1.study;
fn()//输出为 undefined, 因为类内为默认严格模式, 不能指向 window

组件内事件函数的 this 指向

  1. 在 react 中尽量直接使用标签内的事件, 然后单独写一个函数, 传到标签内

  2. react 重写了所有事件函数, onclick 改为 onClick. 因此事件函数内的 this 指向发生变化, 由事件源变为全局

  • 注意:
    • 由于react 中的 jsx 都是表达式赋值的形式, 所以 onclick 后面的大括号内必须是 demo 而不写小括号
    • 如果写了小括号就相当于把 demo 的返回值赋值给 onClick, 此处为 undefined
  1. weatherChange 函数是通过把函数赋值给 onClick, 通过全局调用 onClick. 它不是通过实例调用的, weatherChange 内的 this 指向就会变为 window. 又因为开起来局部严格模式, 这里的 this 就会指向 undefined.

    • 解决方法 1: 其实这里和原生 js 是一样的, 原生 js 的时间函数的 this 是指向事件源, 解决办法一般就是箭头函数使 this 指向变为上下文的 this指向.

    • 解决方法 2: 通过 bind 函数将原型上的 weatherChange 函数的 this 指向重新指到类的属性上

      • 注意: bind 函数返回一个带有新 this指向的新函数, 但并不自动执行
      constructor(props) {
         super(props);
         this.state = {isHot:false};
       	 this.changeWeather = this.changeWeather.bind(this)
      }
      

state 需要通过 setState 来更新

  1. setState 方法继承于 React.Componant

  2. setState 传入参数为一个对象, 此对象会和 state 内的 key:value 比较并且合并, 所以可以做到只更新一个或者部分键值对.

constructor 和 render的调用次数

  • constructor 只调用一次
  • render 调用 n+1 次, 初始一次和每次 state 更新
    • 不用像原生 js 那样把 render 传进事件函数了

基本的代码结构

   <script type="text/babel">
      //1.创建组件
      class Weather extends React.Component {
         constructor(props) {
            super(props);
            //初始化状态
            this.state = { isHot: true };
            this.newWeatherChange = this.weatherChange.bind(this);
         }
         weatherChange() {
            let { isHot } = this.state;
            this.setState({ isHot: !isHot });
         }
         render() {
            //读取状态
            let { isHot } = this.state;
            return <h2 onClick={this.newWeatherChange}> 今天天气很 {isHot ? '炎热' : '凉爽'}</h2>
         }
      }
      //2.渲染组件
      ReactDOM.render(<Weather />, document.getElementById('test'))
   </script>

代码的精简(函数从原型移到属性)

js 类内, 构造器外可以直接写赋值语句, 前面不加 let 和 const, 赋值后直接就是追加的一个属性. 因此, state 的赋值可以拿到构造器外. 同时, 函数也可以写成赋值的形式, 这样函数会从原型变为实例的属性.

<script type="text/babel">
      //1.创建组件
      class Weather extends React.Component {
         state = { isHot: true };
         render() {
            //读取状态
            let { isHot } = this.state;
            return <h2 onClick={this.weatherChange}> 今天天气很 {isHot ? '炎热' : '凉爽'}</h2>
         }
         weatherChange = ()=> {
            let { isHot } = this.state;
            this.setState({ isHot: !isHot });
         }
      }
      //2.渲染组件
      ReactDOM.render(<Weather />, document.getElementById('test'))
</script>

总结

  1. render 函数内 this 指向组件实例对象
  2. 组件实例原型上的函数 this 因为react重写的事件函数全局调用会指向 undefined, 如要更改指向到实例:
    1. 可将函数通过箭头函数定义在组件属性上
    2. 在构造器内新写一个函数, 通过 bind 函数更改 this 指向返回新函数赋值给新写的函数

2. 类式组件实例三大属性之 props

只读属性, 但可以进行运算.

两种方式往组件内传递 props:

  1. 标签内直接书写
  2. 标签内用…展开一个对象
//1.创建组件
class Person extends React.Component {
   render() {
      const {name,age,gender} = this.props;
      //读取状态
      return <ul>
               <li>姓名: {name}</li>
               <li>性别: {gender}</li>
               <li>年龄: {age} </li>
            </ul>
   }
}
const person = {name: "张三",age: "18",gender: "男"}
//2.渲染组件
ReactDOM.render(<Person name="张三" age="18"gender="男"/>,document.getElementById('test'))
ReactDOM.render(<Person {...person}/>, document.getElementById('test2'))

对传入的对象内数据类型进行限制

需要引入prop-types.js 文件来调用对应的限制方法

Person.propTypes = {
   name: PropTypes.string.isRequired,
   age: PropTypes.number,
   gender: PropTypes.string.isRequired,
   speak: PropTypes.func
}
const person = {name: "李四",age:  20,gender: " 女"}
function speak() {

}
//2.渲染组件
ReactDOM.render(<Person name="张三" age={18} gender="男" speak = {speak}/>, document.getElementById('test'))
ReactDOM.render(<Person {...person}/>, document.getElementById('test2'))

标签属性的简写方式

class Person extends React.Component {
   static propTypes = {
      name: PropTypes.string.isRequired,
      age: PropTypes.number,
      gender: PropTypes.string.isRequired,
      speak: PropTypes.func
   }
   static defaultProps = {
      gender: "男",
      age: 18
   }
   render() {
     //读取属性
      const { name, age, gender } = this.props;
      return <ul>
         <li>姓名: {name}</li>
         <li>性别: {gender}</li>
         <li>年龄: {age + 99} </li>
      </ul>
   }
}

类式组件内构造器的问题

构造器的主要作用参考官网只有两点:

  1. 初始化 state: this.state = {name:"Sam"}

  2. 通过 bind 函数改变原型上函数的 this 指向并赋值给一个新的函数, 用作事件函数来保证 this 指向组件实例.

以上两点都可以通过构造器外赋值语句的形式达到同样的效果, 所以构造器不是必须的. 基本不会用到.

函数式组件的 props 应用

函数式组件通过传参的形式把 props 传入组件内部. 属性的限制也可以通过组件名.propTypes 来设置.

//1.创建组件
function Person(props) {
   const {name, age} = props
   return(
      <ul>
         <li>姓名: {name}</li>
         <li>性别: 男</li>
         <li>年龄: {age + 99} </li>
      </ul>
   )
}
//对标签属性进行必要性的限制
Person.propTypes = {
   name: PropTypes.string.isRequired,
   age: PropTypes.number,
   gender: PropTypes.string.isRequired,
   speak: PropTypes.func
}
const person = { name: "李四", age: 20, gender: " 女" }
//2.渲染组件
ReactDOM.render(<Person {...person} />, document.getElementById('test2'))

总结

  1. props 有两种方式传入类式组件的内部:

    1. 直接在ReactDOM.render 函数的一个参数的标签内写键值对
    2. 在组件外部定义一个对象 p, 在render 函数的一个参数的标签内写 {...p}
  2. 可以对传入的 props 的类型进行判断

    1. 类式组件直接在组件内写 static propTypes ={}
    2. 函数式在组件外写 组件名.propTypes = {}
  3. 要引入 prop-types.js 来使用 props 的内容判断

  4. 组件内部不要修改 props

3. 类式组件实例三大属性之 refs

refs 感觉就像js 中自定义的一个标签属性, 用来表姐需要访问的标签. 它可以在组件实例中的属性中以对象的形式找到, 每一个 ref 是一个 key, 对应着标记的标签为值.

通过字符串形式创建 ref (不推荐使用)

//1.创建组件
class Demo extends React.Component {
   showData = () => {
      const input = this.refs.input1
      alert(input.value)
      console.log(this);
   }
   showData2 = () => {
      const input = this.refs.input2
      alert(input.value)
   }
   render() {
      return (
         <div>
            <input ref="input1" type="text" placeholder="点击按钮提示数据" />
            <button ref ="button1" onClick={this.showData}> 点我提示左边数据</button>
            <input ref="input2"onBlur={this.showData2} type="text" placeholder="是去焦点提示数据" />
         </div>
      )
   }

}

//2.渲染组件
ReactDOM.render(<Demo />, document.getElementById('test2'))

通过回调函数形式创建 ref

回调函数的定义:

  1. 我定义了函数

  2. 我没调用函数

  3. 别人调用了函数

ref 里回调函数(内联函数)的参数就是 ref 所在的标签, 参数一般就取名 currentNode 简写为 c.

这里之所以叫回调函数, 是因为我定义了函数, 但是并非自己调用的, 而是 react 帮我调用的.

注意下面的 this.input1 = c 实际上不把c 节点传入了 refs 中去, 而作为组件本身的属性.

class Demo extends React.Component {
 showData = () => {
    const {input1} = this
    alert(input1.value)
    console.log(this);
 }
 showData2 = () => {
    const {input2} = this
    alert(input2.value)
 }
 render() {
    return (
       <div>
          <input ref={(c)=>{this.input1 = c}} type="text" placeholder="点击按钮提示数据" />
           <button ref ="button1" onClick={this.showData}> 点我提示左边数据</button>
           <input ref={(c)=>{this.input2 = c}} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
         </div>
      )
   }

}

//2.渲染组件
ReactDOM.render(<Demo />, document.getElementById('test2'))
回调函数为内联函数设置 ref时函数的执行次数
  1. 设置内联函数输出 ref 节点
  2. 第一次 render 只输出一次
  3. 更改 state 后, 自动调用 render 会输出一个 null 和一个 ref 节点

官方解释是因为第一个 null 是要清空之前的节点. 如果要取消这种情况, 可以写组件类的函数, 然后在 ref 中引用. 正常可以写内联.

通过createRef API 来创建 ref

  1. 通过在组件内创建容器来存储 ref 传回的节点.
  2. 这个容器只能存放一个节点. 也就是说每个 ref 节点都要创建一个容器来接收.
  3. 容器实际上是一个对象, key 值为 current, value 值为节点.
  4. 经过测试, 组件自身 refs 属性就不再存节点了.
//1.创建组件
class Demo extends React.Component {
   input1 = React.createRef()
   showData = () => {
      alert(this.input1.current.value)
      console.log(this);
   }
   render() {
      return (
         <div>
            <input ref={this.input1} type="text" placeholder="点击按钮提示数据" />
            <button onClick={this.showData}> 点我提示左边数据</button>
         </div>
      )
   }
}
//2.渲染组件
ReactDOM.render(<Demo />, document.getElementById('test2'))

React 的事件处理函数

  1. 通过 onXxx 属性指定事件处理函数, 首字母大写
    1. React 自己写了一套事件处理函数, 所以要首字母大写
    2. 事件同样是事件委托给往外传递事件
  2. 通过 event.target 的得到事件发生的 DOM 对象
    1. 因为 ref 不可以过度使用, 因此可以通过这种方式来获取 dom
//1.创建组件
class Demo extends React.Component {
   showData = () => {
      const {input1} = this
      alert(input1.value)
      console.log(this);
   }
   showData2 = (e) => {

      alert(e.target.value)
   }
   render() {
      return (
         <div>
            <input ref={(c)=>{this.input1 = c}} type="text" placeholder="点击按钮提示数据" />
            <button ref ="button1" onClick={this.showData}> 点我提示左边数据</button>
            <input onBlur={this.showData2} type="text" placeholder="是去焦点提示数据" />
         </div>
      )
   }
}
//2.渲染组件
ReactDOM.render(<Demo />, document.getElementById('test2'))

React 中收集表单数据

1.非受控组件

获取数据的时候, 现用现取. 比如表单,CheckBox, 数据需要触发点击事件才能获取到.

//1.创建组件
class Login extends React.Component {
  // 点击提交按钮后
   handleSubmit = (event)=> {
     //防止页面自动跳转, 不跳转通过 ajax 处理数据.
      event.preventDefault();
      const {name,password} = this
      alert(`用户名:${name.value}, 密码:${password.value}`)
   }
   render() {
      return (
         <form onSubmit={this.handleSubmit}>
            用户名: <input ref={(e)=>{this.name = e}} type="text" />
            密码: <input ref={(e)=>{this.password = e}} type="password"/>
            <button>登录</button>
            </form>
      )
   }
}
//2.渲染组件
ReactDOM.render(<Login/>, document.getElementById('test'))

2.受控组件

通过 onChange 事件来随时获取数据. 优势是 refs 用的少了, 因为 react 官网说少用 refs.

//1.创建组件
class Login extends React.Component {
   state = {
      username:"",
      password:""
   }
   saveUsername = (event)=> {
      this.setState({username:event.target.value})
   }
   savePassword = (event)=> {
      this.setState({password:event.target.value})
   }
   handleSubmit = (event)=> {
      event.preventDefault();
      const {username,password} = this.state
      alert(`用户名:${username}, 密码:${password}`)
   }
   render() {
      return (
         <form onSubmit={this.handleSubmit}>
            用户名: <input onChange={this.saveUsername} type="text" />
            密码: <input onChange={this.savePassword} type="password"/>
            <button>登录</button>
            </form>
      )
   }
}
//2.渲染组件
ReactDOM.render(<Login/>, document.getElementById('test'))

高阶函数-函数柯里化

In mathematics and computer science, currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each takes a single argument.

Currying 实际上就是把 f(a,b,c) 转化为 f(a)(b)(c), 因此 f(c) 要返回函数并且接收b参数.

高阶函数

高阶函数只需满足两点中的其中一点:

  1. 参数中有函数
  2. 返回值中有函数

常见的高阶函数: Promise, setTimeout, arr.map(), 事件函数

运用在 react 中的情景

事件处理函数在传递的时候, 不能写括号, 不然就会传函数的返回值. 因此, 如果想要传参数加括号, 就要在返回值里写真正的事件传递函数.

//1.创建组件
class Login extends React.Component {
   state = {
      username:"",
      password:""
   }
   /*高阶函数:
      1. 参数里有函数
      2. 返回值里有函数
   */
   saveFormData = (formData)=> {
      return (evnet)=> {
         //注意中括号内 key 来取value, 因为下面传参的时候传的字符串
         this.setState({[formData]:event.target.value})
      }
   }
   handleSubmit = (event)=> {
      event.preventDefault();
      const {username,password} = this.state
      alert(`用户名:${username}, 密码:${password}`)
   }
   render() {
      return (
         <form onSubmit={this.handleSubmit}>
            用户名: <input onChange={this.saveFormData("username")} type="text" />
            密码: <input onChange={this.saveFormData("password")} type="password"/>
            <button>登录</button>
            </form>
      )
   }
}
//2.渲染组件
ReactDOM.render(<Login/>, document.getElementById('test'))

不使用柯里化的事件函数

//1.创建组件
class Login extends React.Component {
   state = {
      username: "",
      password: ""
   }
   saveFormData = (formData, event) => {
      this.setState({ [formData]: event.target.value })
   }
   handleSubmit = (event) => {
      event.preventDefault();
      const { username, password } = this.state
      alert(`用户名:${username}, 密码:${password}`)
   }
   render() {
      return (
         <form onSubmit={this.handleSubmit}>
            用户名: <input onChange={event => this.saveFormData("username", event)} type="text" />
            密码: <input onChange={event => this.saveFormData("password", event)} type="password" />
            <button>登录</button>
         </form>
      )
   }
}
//2.渲染组件
ReactDOM.render(<Login />, document.getElementById('test'))

Avatar
Aaron Fan My research interests include machine learning, signal processing, web development and robotics.