React一些简单的学习记录

321 阅读7分钟

react 学习记录

如何运行一个react项目

在刚开始的学习中,要创建运行一个react项目,是使用CRA来创建,使用create-react-app

1、使用create-react-app创建项目先下载  npm i create-react-app -g

2、直接使用npx create-react-app demo(项目名)就可以创建

3、创建好执行 yarn start 就可以运行

JSX

react中使用JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式,这里对jsx不做过多的介绍,下面是jsx介绍网站

https://react.docschina.org/docs/introducing-jsx.html

组件

react中一个重要的核心概念:组件 组件可以重复使用,并且不用担心使用冲突。减少很多的冗余代码

react在组件有:1、类组件 2、函数组件

在16.7以前函数组件也称无状态组件,只用来展示,再没有其他功能。在hooks出现之后,函数组件也可以做很多的功能,和类组件同样的重要

// 函数组件
function List(props){
    return <p>React</p>
}
// 类组件  继承react的Component类

class List extends Component{
    render(){
        return <p>React</p>
    }
}

类组件

使用类组件必须继承react的React.Component这个类,并且如果如组件要给子组件传值,则在子组件中必须要constructor中使用super(props),否则子组件中无法访问传来的值

props值

// 父组件 使用属性给子组件传值
class App extends Component {
  render() {
    return (
      <div className="friend-list">
        {data.map((item, index) => {
          return <List data={item} key={index}></List>;
        })}
      </div>
    );
  }
}

// 子组件 要获取到父组件的值需要使用super()
export default class List extends Component {
  constructor(props) {
    super(props); // 使用super() 相当于执行父类中的语句,具体原理可以看es6
    this.state = { showChild: false };
  }
}

要注意的是:如果子组件也需要有自身的状态,则必须写在super()语句之后,否则会被覆盖,无效。

还有需要注意的是由父组件的props传入的值是单向的,子组件不可以更改

props的值和state都可以决定组件的状态和显示,state可以在组件内部修改

再说state值之前先说一下类组件的this问题,主要体现在事件调用上面

this是谁调用就指向谁,所以经常会出现this指向不对或者this下不存在调用方法的问题

1、在组件中使用函数声明的方法,在使用this.fun()的时候获取不到fun这个方法,这是因为this指向的类下没有fun这个方法,需要绑定

export default class List extends Component {
  constructor() {
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handler = this.handler.bind(this);
  }
  function handle() {
   console.log('bind')
  };
  render(){
      return <p onClick={this.handler}>{this.state.name}</p> // 使用方法
  }
}

2、如果觉得上面每次定义一个方法都要绑定麻烦的话可使用箭头函数定义方法,因为箭头函数的this指向的是定义函数时候所在的位置,所以this指向的是定义当时的类,就可以直接调用this.handle使用

export default class List extends Component {
  handle=()=> {
   console.log('bind')
  };
  render(){
      return <p onClick={this.handler}>{this.state.name}</p> // 使用方法
  }
}

state值

在类组件中如果要定义自己的组件状态,需要使用state来定义,例如

使用this.setState()去修改

export default class List extends Component {
  constructor() {
    this.state = { name: 'tom' };
  }
  handler = () => {
    this.setState({
      name: 'job'
    });
  };
  render(){
      return <p onClick={this.handler}>{this.state.name}</p> // 使用方法
  }
}

setState()方法是异步的,可以接收两个参数 1、一个对象 2、回调函数

因为是异步的,如果在修改完一个值之后想获取到这个值做一些操作就会出现意外,要想正确的修改有两个办法:

1、setState()可以传入回调函数,执行想执行的 2、使用生命周期,在组件更新之后执行

// 回调函数
this.setState({
      name: 'job'
    },()=>{
        console.log(this.state.name) // job
    }
)

组件之间通信

不管是react还vue中,数据都是单向流动的,有父级传递给子组件的数据,子组件不可以修改

父级传递给子级

由父级使用props属性给子组件传值,子组件中从props中获取

子组件像父级通信

因为子组件不可以直接修改父组件传递的值,所以想要修改也需要从父组件传递来修改操作的方法,子组件进行调用

跨组件通信 使用context可以实现跨组件通信,但是一般项目中最好不要使用,就先不记录了

类组件的生命周期

--挂载阶段

1constructor   constructor在类被实例化的时候就会执行,所以这里可以作为生命周期的挂载阶段

--挂载完毕

1componentDidMount()方法是在dom被渲染完成之后运行,这里就可以获取dom元素

--更新阶段

1render()函数在组件更新时候会执行
2shouldComponentUpdate() --这里判断是否更新,在这里做优化
3componentDidUpdate()--更新完成

--卸载阶段

1、componentWillUnmount  组件卸载时候执行  这里一般做清除定时器或者清除一些监听


函数组件

函数组件本质就是一个常规函数,返回一个reactElement。函数组件中没有this和生命周期函数

函数组件中是使用hook去改变状态,在16.8之后要改变组件状态就不需要再编写类组件就可以改变,和类组件是等效的

常用hook

1、常用的useState()

函数组件中要修改状态并且触发diff更新是依靠useState触发响应式

用法

const [counter,setCounter] = useState(2)

同一个组件中可以使用多个useState进行多个状态定义
useState()返回的setState方法,不会进行对象合并,所以需要手动进行合并
注意 useState 返回的 setState 


2、useEffect()

当组件更新及挂载时候会执行这个函数

虽然函数组件没有生命周期,但是useEffect可以模拟类组件中各个生命周期

// 挂载和组件更新时候更新都会执行
useEffect(()=>{
    console.log("挂载和更新都会执行")
})

// 挂载和counter更新都会执行
useEffect(()=>{
    console.log("挂载和挂载counter更新都会执行")
},[counter])

// 只在挂载时候执行
useEffect(()=>{
    console.log("只在挂载时候执行")
},[])


// useEffect中的回调函数在卸载时候执行,如果useEffect有依赖项,则每次在依赖项更新时候回调函数也会执行
useEffect(()=>{
    return () => {
      console.log("卸载了"); //sy-log
    };
},[])

3、useRef()

 用户关联原生DOM节点,或者用来记录组件更新前的一些数据
 

如何关联dom的节点呢

和creatRef使用方式一样,不过这个是只能在函数组件中使用。creatRef在类组件中使用

import { useEffect, useRef, useState } from "react";
function Test(props) {
    let inputEl = useRef(null);
    function add() {
    console.log(inputEl.current.value); //input的value值 element元素都在current中
  }
    return (
    <div>
      <input
        type="text"
        ref={inputEl}
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
        }}
      />
      <button onClick={add}>add</button>
    </div>
  );
}

4、memo

还有当react组件是一个列表的时候,会发现一个问题,就是每次数据中添加一条数据,其他子组件也会重新渲染,这样很浪费资源,所以使用Memo进行优化


// 父组件
import Todo from "./Todo";

import { useSelector } from "react-redux";
function Todos(props) {
  const todos = useSelector((state) => state.todos);
  return (
    <ul id="todo-list">
      {todos.map((item) => {
        return <Todo key={item.id} data={item}></Todo>;
      })}
    </ul>
  );
}

export default Todos;


// 子组件
function Todo(props) {
    // todo
}

export default memo(Todo, (props1, props2) => {
  return props1.data === props2.data;
});

上面的例子乳沟父组件中的todos变化,如果子组件不使用memo则所有的子组件都会更新

memo是比较传入的数据值是否相同,如果相同则不渲染,如果不同则更新,是一个优化点

这里在使用memo之前,我一直疑惑为什么react中列表组件如果修改一个则所有的都会更新,在vue中就没有这个问题,后来突然想到了

如果用到引用类型的,在vue2中是使用object.defineproperty()数据劫持添加依赖,更新时候去执行渲染视图,数据地址没有改变,是对比的属性值是否一样去diff修改

而react是如果用到引用类型修改要渲染到视图,就必须的修改地址才能渲染到视图,如果地址不变就监听不到变化的

因为地址的改变,所以之前todo-list在修改或者添加时候,所有的list项都会被重新渲染,是因为整个对象地址都被改变了。 优化使用memo去比较值是否改变,改变才去重新渲染。 这部分的优化在vue中是vue帮着做了,在react中是需要自己去做的

注意

hooks只能在函数组件中使用,类组件中是不能使用的

hooks的优势

--简化组件的逻辑

--复用状态逻辑

--无需使用类组件编写(可以无视this了)