02_React基础语法

react

  • 是一个用于构建用户界面的 javascript 库

1 hello world

  • 需要安装两个包: npm install react react-dom
  • 要支持 jsx 语法,需要 react
  • 要支持 dom 渲染,需要 react-dom
// 引入react支持jsx语法
import React from "react"; // 此处导入的变量名必须是React
import { createRoot } from "react-dom/client";

// 先体验jsx语法书写我们的html结构
const ele = <h1>hello world</h1>;

// 把上面的html结构渲染到#app里面
const root = createRoot(document.getElementById("app"));
root.render(ele);

2 JSX

  • jsx 语法不是字符串也不是 html
  • 更偏向 js,具有 js 的完整功能
  • 最外层如果是一个元素可以直接写
  • 最外层如果是多一个元素要写在数组里面,最外层的数组外面不用写{}
  • 标签的属性名需要小驼峰命名,属性 class 要写成 className
  • 如果属性值是字符串用引号包括,如果是 js 表达式用{}, 引号和{}不能同时使用
  • Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用,最后会生成一个 react 元素
  • 具体代码见:src/12k/01learnJsx.jsx

3 类组件和函数组件

  • 书写组件最简单的方法是函数组件
  • 类组件里面有 this 是组件实例对象
  • 函数组件里面没有 this,this 是 undefined
  • 自定义组件名要求首字母驼峰命名
  • 类组件语法 - 利用 es6 的 class 语法
  • 具体代码见:src/12k/02learnComponent.jsx
class MyHome extends React.Component {
  // 里面必须实现render方法
  render() {
    // return的内容就是组件的html结构
  }
}
  • 函数组件的语法
function MyFooter() {
  // return的内容就是组件的html结构
}

4 props

  • 自定义组件可以当标签使用,标签上都有属性
  • 书写标签上的任意属性,在自定义组件内部可以通过 props 接收
  • 书写标签内的元素,在自定义组件内容可以通过 props 接收
  • 类组件通过: this.props 接收,其中标签内的元素通过 this.props.children 接收
  • 函数组件通过: 函数的第一个形参接收 props,标签内的元素通过 props.children 接收
  • 自定义组件和原生的 dom 元素的区别
    • 原生的 dom 元素是小写的,自定义组件是首字母大写的
    • 原生的标签上的属性值最后一定是字符串,自定义组件上的属性值可以是任意类型
    • 自定义组件上的属性如果只写属性不写值,表示值是 true
  • 在自定义组件内部不要修改 props,是只读的
  • 具体代码见:src/12k/03learnProps.jsx

5 元素渲染

  • 一个 react 元素一旦创建,就不可变
  • 如果要更新一个 react 元素
  • 目前只能重新创建一个新的 react 元素
  • 然后渲染到根节点中
  • 渲染 dom 元素的时候,会把本次 react 元素对象和上一次的 react 元素对象对比
  • 只更新必要的部分
  • 具体代码见:src/main.jsx的第四个部分

6 props 类型检查

  • 官方推荐: prop-types
  • 在 v15.5 之前包含在 react 里面,后面独立成一个库:prop-types
  • 安装:npm install prop-types -S
  • 使用
import PropTypes from "prop-types";
import React from "react";
class MyHome extends React.Component {
  render() {}
}
// 设置MyHome接收的属性props的类型和是否必须
MyHome.propTypes = {
  name: PropTypes.string.isRequired, // name是字符串类型,而且是必须传入的,有默认值算传入了
  age: PropTypes.number,
  love: PropTypes.array,
  fn: PropTypes.func,
  bool: PropTypes.bool,
  obj: PropTypes.object,
  node: PropTypes.node, // react可以直接渲染的类型
  element: PropTypes.element, // react元素
  message: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  gender: PropTypes.oneOf(["男", "女"]),
};
MyHome.defaultProps = {
  name: "lucy",
};

const ele = <MyHome name="lily" {...}/>;

7 在 jsx 语法中书写注释

  • jsx 是偏 js,所以注释是 js 代码,也要写在{}里面
  • 最后注释就下面这个样子,不能使用 双斜杠
conse ele = <div>
  {/* 我是注释 */}
  hello world
<div>

8 回忆原生 js 中的 this 指向问题

  • 如果是事件处理函数调用,里面的 this 是事件源,里面的 event 是事件对象
  • 如果是普通函数调用,里面的 this 非严格模式是 window,严格模式是 undefined
  • 如果是对象的方法调用,里面的 this 是当前调用方法的对象
  • 如何改变 this 的指向
    • fn(实参 1,实参 2,…) - 没有改变 this 的指向
    • fn.call(改变成的 this 指向,实参 1,实参 2,…) - 只改变本此调用的 this
    • fn.apply(改变成的 this 指向,[实参 1,实参 2,…]) - 只改变本此调用的 this
    • let newFn = fn.bind(改变成的 this 指向) - 不调用函数,生成一个新的函数 newFn,newFn 不管怎么调用 this 都固定

9 state - 类似 vue 里面的 data

  • state 只能在构造函数中初始化赋值
  • state 不能直接修改,要通过 setState 修改
  • setState 的更新可能是异步的
  • setState 的更新会进行浅合并,只合并同一个函数域
  • 如果 setState 修改的值依赖上一次的值,setState 传入的参数就必须是一个函数
  • 语法 1: 不依赖上一次的值
this.setState(
  {
    num: 100,
  },
  () => {
    // 第二个参数的回调函数会在更新完成以后调用
    console.log(this.state);
  }
);
  • 语法 2: 依赖上一次的值
this.setState((state) => {
  return {
    num: state.num + 1,
  };
});

10 组件生命周期 - lifeCycle

  • 组件生命周期分为 3 大部分
    • 挂载时:constructor,render,componentDidMount
    • 更新时:shouldComponentUpdate,render,componentDidUpdate
    • 卸载时:componentWillUnmount
  • constructor
    • 构造函数,第一行:super()
    • 初始化组件状态(初始化声明式变量):this.state = {count:1,num:100}
      • 构造函数中可以直接 state 赋值,不要写 setState
    • 不要在构造函数中开定时器,不要调用接口
  • 挂载时: render
    • 挂载时必定执行一次 render,不能通过 shouldComponentUpdate 阻止
    • render 是类组件必须的一个生命周期
    • 返回值是视图结构(JSX,Fiber 树,React 元素)
    • 可以做数据处理,一般是解构一些数据
    • 不调用接口,不开启定时器,不要写 setState
  • componentDidMount
    • react 元素变成了 dom
    • 此时数据变成了真实的 dom
    • 可以初始化一些需要 dom 的操作,可以开启定时器,可以请求接口
  • 更新时:render
    • 如果有 new Props,setState,forceUpdate,会触发 render 函数
    • 但是:
    • forceUpdate 一定触发 render 函数
    • new Props,setState 在触发 render 函数之前会进过 shouldComponentUpdate
    • 如果不写 shouldComponentUpdate,就必然进入 render
  • shouldComponentUpdate - react 的性能优化点
    • 有两个参数:
    • 第一个参数是:nextProps
    • 第二个参数是:nextState
    • 返回值是布尔值,如果是 true,就进入 render,如果是 false,就不进入 render
    • 我们理想中的 shouldComponentUpdate 应该是,props 或者 state 有变化才进入 render,如果没有变化就不进入 render
    • 我们自己写难度大,所有可以继承 React.PureComponent,他帮我们实现了基本的 shouldComponentUpdate 的上述功能
  • componentDidUpdate
    • 有两个参数:
    • 第一个参数是:prevProps
    • 第二个参数是:prevState
    • 新的 dom 节点,更新完成,依赖新的 dom 的操作可以写在这里
    • 不要写定时器,不要调用接口,不要写 setState
  • componentWillUnmount
    • 组件卸载的时候调用的函数
    • 可以在里面进行清理操作,一般是:清除定时器,取消请求等

11 事件处理

  • react 的事件处理和 dom 的事件处理基本一致
  • react 的事件名称用小驼峰命名
  • react 的事件处理函数就是一个函数,不是字符串
  • 事件处理函数没有绑定 this,需要我们自己绑定 this
class MyHome extends React.PureComponent {
  constructor() {
    super();
    this.clickHandler = this.clickHandler.bind(this);
  }
  clickHandler(e) {
    console.log(this);
    console.log(e);
    // 这个e不是原生的事件对象,是经过react加工的,叫做合成事件,解决了兼容性问题
  }
  render() {
    return (
      <div>
        <h1 onClick={this.clickHandler}>thisundefined,e是合成事件</h1>
        <h1
          onClick={(e) => {
            this.clickHandler(e);
          }}
        >
          this是组件实例对象,需要在箭头函数中获取合成事件,作为实参传递下去
        </h1>
        <h1 onClick={this.clickHandler}>this是组件实例对象,也有事件对象传入</h1>
      </div>
    );
  }
}

12 非受控组件 - 不推荐

  • 没有设置表达的受控属性的值,就是非手动组件
  • 非受控组件通过 dom 操作来获取表单元素的值

13 受控组件 - 推荐

  • 设置了受控属性的值,就是受控组件
  • 可以受控的表单属性主要有
    • value: text,password,color,select,…
    • checked: radio,checkbox
  • 受控组件通过设置受控属性的值,和 onChange 事件来设置和获取表单元素的值
  • 如果手动属性是 value,我们一般写成:
class MyForm extends React.PureComponent {
  constuctor() {
    super();
    this.state = {
      name: "",
    };
  }
  changeHandler(e, key) {
    this.setState({
      [key]: e.target.value,
    });
  }
  render() {
    const { name } = this.state;
    return (
      <div>
        <input
          type="text"
          value={name}
          onChange={(e) => this.changeHandler(e, "name")}
        />
      </div>
    );
  }
}
  • 如果受控的属性是 radio 的 checked,但是我们需要的数据是 value
class MyForm extends React.PureComponent {
  constuctor() {
    super();
    this.state = {
      gender: "man",
    };
  }
  changeHandler(e, key) {
    this.setState({
      [key]: e.target.value,
    });
  }
  render() {
    const { gender } = this.state;
    return (
      <div>
        <input
          type="radio"
          value="man"
          checked={gender == "man"}
          onChange={(e) => this.changeHandler(e, "gender")}
        /><input
          type="radio"
          value="woman"
          checked={gender == "woman"}
          onChange={(e) => this.changeHandler(e, "gender")}
        /></div>
    );
  }
}
  • 如果受控的属性是 checkbox 的 checked,但是我们需要的数据是 value 的集合
class MyForm extends React.PureComponent {
  constuctor() {
    super();
    this.state = {
      love: ["football", "basketball"],
    };
  }
  changeHandler(e,key) {
    const {value,checked} = e.target
    this.setState((state)=>{
      [key]:checked?[...state.love,value]:state.love.filter(item=>item!=value)
    })
  }
  render() {
    const { love } = this.state;
    return (
      <div>
        <input
          type="checkbox"
          value="football"
          checked={love.includes("football")}
          onChange={(e) => this.changeHandler(e, "love")}
        />
        足球
        <input
          type="checkbox"
          value="basketball"
          checked={love.includes("basketball")}
          onChange={(e) => this.changeHandler(e, "love")}
        />
        篮球
        <input
          type="checkbox"
          value="waterfall"
          checked={love.includes("waterfall")}
          onChange={(e) => this.changeHandler(e, "love")}
        />
        水球
      </div>
    );
  }
}

14 状态提升

  • 如果多个组件之间要共享状态,把状态提升到共同的父组件中声明
  • 如果要在子组件中修改父组件的状态,需要父组件通过 props 传递修改状态的方法给子组件,传递方法的时候要 bind 父组件的 this
// 子组件
class Input extends React.PureComponent(){
  changeFatherState(){
    const {changeList} = this.props
  }
  // 修改父组件的状态
  render(){
    return ()
  }
}
// 子组件
class List extends React.PureComponent(){
  // 子组件通过props接收父组件的数据
  render(){
    const {data} = this.props
    return ()
  }
}

class Todos extends React.PureComponent(){
  constructor(){
    super()
    this.state = {
      list:[]
    }
  }
  changeList(){
    // 这个方法可以修改list
  }
  render(){
    return <>
      <Input changeList={this.changeList.bind(this)}/>
      <List data={list}/>
    </>
  }
}

15 context

  • 可以通过 React.createContext 创建一个上下文对象,比如:MyContext
    • 语法:React.createContext(上下文对象的默认值)
  • 任意类组件可以通过: 组件名.contextType = MyContext,来使用 MyContext 这个上下文对象
  • 在类组件内部通过:this.context 来获取上下文对象
  • 如果前面的祖先没有通过 value 来给 MyContext 赋值,我就用 MyContext 的默认值
  • 可以通过MyContext.Provider组件的 value 属性来给 MyContext 赋值
    • 赋值会完全覆盖默认值,只影响当前组件的子子孙孙
  • 如果有多个 MyContext.Provider 设置 value 属性,使用最近的

16 Portal

  • 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案
  • 就是使用 react-dom 包里面的 createPortal 方法
  • 语法:createPortal(react 元素,要插入到的 dom 节点)

17 Refs&DOM

  • refs 提供了一种获取 dom 节点和 react 元素的方法
  • 方法 1: React.createRef()
class MyHome extends React.PureComponent {
  constructor() {
    super();
    // 步骤1: 通过React.createRef()创建一个refs
    this.myRef = React.createRef(); // {current:null}
  }
  componentDidMount() {
    // 当组件挂载完成后,this.myRef = {current:dom节点}
  }
  render() {
    return (
      <div>
        {/* 步骤2:附加到html原生标签上 */}
        <input type="text" ref={this.myRef} />
        {/* 如果附加到组件标签上,那么this.myRef  = {current:组件实例对象} */}
      </div>
    );
  }
}
  • 方法 2: 回调 refs - 即可以绑定也可以解绑
class MyHome extends React.PureComponent {
  constructor() {
    super();
    this.handler = (val)=>{
      // 把实参的dom元素或者组件实例给其他方法使用
      this.myInput = val;
    }
  }
  render() {
    return (
      <div>
        {/* ref的值是一个函数,这个函数会在组件挂载完成的时候传入实参,就是当前附加ref的dom元素或者组件实例,当组件卸载的时候,会传入实参null */}
        <input type="text" ref={} />
      </div>
    );
  }
}

18 refs 转发

  • 将 ref 自动地通过组件传递到其一子组件的技巧
  • 重用的组件库是很有用的
  • 一般组件库里面的哪些组件会有 refs 转发:Input,Button,…
  • 这些组件倾向于以一种类似常规 DOM button 和 input 的方式被使用,并且访问其 DOM 节点
const Input = React.forwardRef((props, ref) => {
  // React.forwardRef()
  // ()里面是一个函数组件,这个函数组件在被React.forwardRef调用的时候
  // 会传入两个参数,第一个是props
  // 第二个是要转发的ref,可能是当前组件的子组件,也可能是当前组件内的dom几点
  return <input type="text" {...props} ref={ref} />;
});

class MyHome extends React.PureComponent {
  constructor() {
    super();
    this.input = React.createRef();
  }
  componentDidMount() {
    // this.input.current其实就是Input组件里面的input那个dom节点
    this.input.current.focus();
  }
  render() {
    return (
      <div>
        <h1>MyHome</h1>
        <Input ref={this.input} />
      </div>
    );
  }
}

19 高阶组件

  • 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧
  • 高阶组件: 是一个函数,参数是一个组件,返回值也是一个组件
  • 高阶组件不是组件时函数
  • 通过高阶组件可以给组件添加一些额外的信息

20 hooks

  • useState
const [count, setCount] = useState(0);
// 不依赖上次的
setCount(10);
// 依赖上次的
setCount((count) => count + 10);
  • useEffect
useEffect(() => {
  // didMount
  return () => {
    // willUnmount、didUpdate
  };
}, [count]);
  • useMemo
const double = useMemo(() => {
  return count * 2;
}, [count]);
  • useCallback
const fn = useCallback(() => {
  console.log(count);
}, [count]);
  • useRef
const myRef = useRef("inp"); // {current:'inp'}
  • useContext
const myContext = useContext(ThemeContext);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值