react学习笔记

95 阅读2分钟

react核心思想

  • 数据驱动界面更新
  • 组件化

虚拟DOM

基本使用

  • react.js主要用于'生成虚拟DOM'
  • react-dom.js主要用于'将虚拟DOM转换为真实DOM' React.createElement(元素名称,元素属性,元素内容)创建虚拟DOM

ReactDOM.render(渲染元素,挂载到的容器元素,回调函数)渲染虚拟DOM,多次渲染, 后渲染会覆盖先渲染,render方法一次只能渲染一个元素/组件

在react中事件名称采用驼峰的写法,React.createElement('button', {onClick: btnClick}, '按钮');

JSX

  • 作用:JSX作用编写React中的页面结构体,
  • 简介:JSX === JavaScript + X === (XML) === (eXtension),JSX 是一个看起来很像 XML 的 JavaScript 语法扩展

编写规范:

  • 顶层只能有一个根元素,
  • 标签可以是单标签也可以是双标签, 但如果是单标签必须闭合(/>)
  • 建议使用()将JSX代码包裹起来

使用JSX有点:

  • 使用JSX使得我们在React中编写页面结构更为简单、灵活
  • JSX 是类型安全的,在编译过程中就能发现错误
  • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化
  • 防止XSS注入攻击

绑定事件:事件名称必须采用驼峰写法

监听方法this处理:

  • 箭头函数
  • 创建时通过bind修改
  • 注册时通过bind修改
  • 普通函数和箭头函数结合

事件传参:通过箭头函数和普通函数结合方式可以获取传递的参数

<div>
    <button onClick={(e)=>{this.btnClick(1,2,e)}}>按钮</button>
</div>

合成事件对象:合成事件是React在浏览器事件基础上做的一层包装,可通过nativeEvent属性获取浏览器原生事件

浏览器无法识别JSX,导入babel.js,在script标签上添加type="text/babel",借助babel将JSX转换成JS, 转换成React.createElement();

JSX注释编写:在JSX中遇到<会当做XML元素解析, 遇到{会当做JS解析,所以在JSX中不能使用HTML的注释,注释需要写在{}括号中

JSX绑定类名:class在js中是关键字,所以不能直接绑定属性,需要绑定在calssName属性上

JSX绑定动态类名:元素只有单个类名,可以通过三元运算符绑定,多个类名

JSX实现v-if功能和v-show功能:

<p style={{display: this.state.flag?'block':'none'}}>我是段落</p>
{this.state.flag && <p>动态显示</p>}

JSX实现v-for功能:

class App extends React.Component {
    constructor(props) {
        super(props);
    }
    state = {
        list:[[1,2],[3,4,5],[6,7]]
    }
    render() {
        return (
            <div>
                {
                    this.state.list.map((item,index)=>
                        <div key={index}>
                            {
                                item.map((subItem,subIndex)=>
                                    <div key={subIndex}>{subItem}</div>
                                )
                            }
                        </div>
                    )
                }
            </div>
        );
    }
}
<div className={`container ${index===this.state.currentIndex?"active":null}`}></div>

JSX绑定样式:由于样式是键值对形式的, 所以在JSX中如果想要动态绑定样式,必须将样式放到一个对象中, 并且所有以-连接的样式名称都要转换成驼峰命名

<p style={{color:'red', fontSize:'50px'}}>绑定样式</p>

函数组件

通过ES6之前的构造函数来定义(无状态组件),在构造函数中返回组件的结构即可,函数组件也称为无状态组件,

function Home() {
    return (
      <div>
          <div>{message}</div>
          <button onClick={btnClick}>按钮</button>
      </div>
    );
}

类组件

定义一个类, 在这个类中实现render方法, 在render方法中返回组件的结构即可,有状态组件,组件中的状态(state)指的其实就是数据

继承于React.Component的组件, 默认都会从父类继承过来一个state属性

class Home extends React.Component{
    render(){
        return (
            <div>
                <div>{message}</div>
                <button onClick={btnClick}>按钮</button>
            </div>
        )
    }
}

监听方法this指向

React内部在调用监听方法的时候, 默认会通过apply方法将监听方法的this修改为了undefined

父子组件通信

设置默认值以及校验参数类型都是通过实例的静态属性设置,在构造函数中通过

构造函数.属性 = 属性值 方式设置

在类中通过static静态属性设置

函数式组件父传子:

父组件传递的所有数据放到pros对象中传递给子组件
<Son name={'lmh'} age={18}/>

子组件设置参数默认值
Son.defaultProps = {name:"lmh",age:"20"}
子组件校验参数类型,安装prop-types插件
npm install prop-types
Son.propTypes = {name:PropTypes.string,age:PropTypes.number}

类组件父传子:

class Main extends React.Component{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div className={'main'}></div>
        )
    }
    设置参数默认值
    static defaultProps = {
        name: '知播渔',
        age: 666
    }
    校验参数类型
    static propTypes = {
        name: ReactTypes.string,
        age: ReactTypes.number
    }
}

子传父

1.父组件传递一个方法给子组件
2.子组件在调用这个方法的时候, 就可以通过方法传参的形式给父组件传递数据
<Footer fatherFn={this.myFn.bind(this)}/>

跨组件通信父传子

通过context上下文传递, 1:调用React.createContext({})创建上下文,返回生产者组件Provider和消费者组件Consumer,

2:Provider生产者容器组件生产数据,包括需要消费生产组件数据的后代,value属性为生产组者组件生产的数据

3:Consumer消费者容器组件, 专门用于消费生产者容器组件生产的数据的

class App extends React.Component{
    render(){
        return (
            <Provider value={{name:'lnj', age:18}}>
                <Father></Father>
            </Provider>
        )
    }
}
class Father extends React.Component{
    render(){
        return (
            <Consumer>
                {
                    (value)=>{
                        return (
                            <div>
                                <p>{value.name}</p>
                                <p>{value.age}</p>
                            </div>
                        )
                    }
                }
            </Consumer>
        )
    }
}

设置子组件的上下文contextType,消费数据
const AppContext = React.createContext({
    name:'知播渔',
    age: 666
});
Son.contextType = AppContext;
class Son extends React.Component{
    render(){
        return (
            <div>
                {/*3.从当前组件的上下文中消费数据*/}
                <p>{this.context.name}</p>
                <p>{this.context.age}</p>
            </div>
        )
    }
}

跨组件通信子传父,兄弟组件通信

使用event库跨组件通信,npm install events

event库常用api:

  • 监听事件:eventBus.addListener("事件名称", 监听函数)
  • 移除事件:eventBus.removeListener("事件名称", 监听函数)
  • 触发事件:eventBus.emit("事件名称", 参数列表)
全局创建 const eventBus = new EventEmitter();
class A extends React.Component {
    render(){
        return (
            <div>
                <button onClick={this.btnClick.bind(this)}>发送数据给B</button>
            </div>
        )
    }
    btnClick(){
        eventBus.emit('getData','lmh',20)
    }
}
class B extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            name:'',
            age:'',
        }
    }
    componentDidMount(){
        eventBus.addListener('getData',this.getData.bind(this))
    }
    componentWillUnmount(){
        eventBus.removeListener('getData',this.getData)
    }
    getData(name,age){
        this.setState({
            name:name,
            age:age
        })
    }
    render(){
        return (
            <div>
                <span>年龄:{this.state.age}</span>
                <span>姓名:{this.state.name}</span>
            </div>
        )
    }
}
class App extends React.Component {
    render() {
        return (
            <div>
                <A></A>
                <B></B>
            </div>
        )
    } 
}

props和state区别

props用于接收父组件传递的数据,只读

state是组件自己的数据,可修改,

setState是同步的还是异步的

1.setState是同步的还是异步的?

默认情况下setState是异步的

2.为什么setState是异步的?

主要是为了优化性能

3.如何拿到更新之后的数据?

setState方法其实可以接收两个参数,通过setState方法的第二个参数, 回调函数拿到

this.setState({
    age: 111
}, ()=>{
    console.log('回到函数中', this.state.age);
});

4.setState一定是异步的吗?

不一定: 在定时器中, 在原生事件中

componentDidMount() {
    const oBtn = document.getElementById('btn');
    oBtn.onclick = () => {
        this.setState({
            age: 666
        });
        console.log(this.state.age); // 666
    }
}
setTimeout(()=>{
    this.setState({
        age: 666
    });
    console.log(this.state.age); // 666
}, 0);

5.setState是如何给state赋值的

通过Object.assign(),let obj = Object.assign({}, oldObj, newObj)

6:State合并现象

setState会收集一段时间内所有的修改再更新界面,所以就出现了State合并现象

btnClick(){
/*
1.为什么最终的一个值是1, 不是3
因为setState默认是一个异步的方法, 默认会收集一段时间内所有的更新, 然后再统一更新
所以就导致了最终的一个值是1, 不是3
* */
this.setState({
    age: this.state.age + 1
});
// console.log(this.state.age); // 0
this.setState({
    age: this.state.age + 1
});
this.setState({
    age: this.state.age + 1
});
}
let oldObj = {age: 0};
let stateList = [
    // {age: oldObj.age + 1},
    // {age: oldObj.age + 1},
    // {age: oldObj.age + 1},
    // {age: 0 + 1},
    // {age: 0 + 1},
    // {age: 0 + 1},
    {age: 1},
    {age: 1},
    {age: 1}
];
stateList.forEach((newObj)=>{
    // Object.assign({}, {age: 0}, {age: 1}); // {age: 1}
    // Object.assign({}, {age: 1}, {age: 1}); // {age: 1}
    // Object.assign({}, {age: 1}, {age: 1}); // {age: 1}
    oldObj = Object.assign({}, oldObj, newObj);
});

7:解决State合并现象 setState传入函数,函数返回对象形式,函数接收state,props

btnClick(){
    this.setState((preState, props)=>{
        return {age: preState.age + 1}
    })
    this.setState((preState, props)=>{
        return {age: preState.age + 1}
    })
    this.setState((preState, props)=>{
        return {age: preState.age + 1}
    })
}

生命周期方法

1.React组件生命周期方法

组件从创建到销毁的过程, 在特定的时间节点调用的方法

  • constructor函数:组件被创建的时候, 就会调用,通过props接收父组件传递的数据,this.state初始化内部的数据,bind为事件绑定实例(this)
  • render函数:渲染组件的时候, 就会调用,返回组件的网页结构
  • componentDidMount函数:组件已经挂载到DOM上时,操作DOM,发送网络请求,添加订阅
  • componentDidUpdate函数:组件已经发生了更新时
  • componentWillUnmount函数:组件即将被移除时,取消网络请求,清除定时器
  • shouldComponentUpdate函数:组件数据变更,决定是否更新组件,可以获取到组件更新后的数据,返回true则更新UI视图
  • static getDerivedStateFromProps函数:挂载或更新,映射数据到state中,
  • getSnapshotBeforeUpdate函数:更新时最后能获取更新前数据

组件创建时执行的生命周期方法: constructor(构造器)=>static getDerivedStateFromProps(映射数据到state中)=>render(生成结构)=>componentDidMount(挂载组件)

组件更新时执行的生命周期方法:static getDerivedStateFromProps(映射数据到state中)=>shouldComponentUpdate(决定是否更新UI)=>render(重新渲染UI)=>getSnapshotBeforeUpdate(最后可以获取更新前数据)=>componentDidUpdate(更新完成)

16开始生命周期.png

react渲染流程

执行render方法=>将JSX转换成createElement=>执行createElement创建虚拟DOM, 得到虚拟DOM树=>根据虚拟DOM树在界面上生成真实DOM

React更新流程

props/state发生改变=>render方法重新执行=>将JSX转换成createElement=>利用createElement重新生成新的虚拟DOM树=>新旧虚拟DOM通过'diff算法'进行比较=>每发现一个不同就生成一个mutation=>根据mutation更新真实DOM

React-Diff算法

  • 只会比较同层元素=>根元素类型不同则直接生成新的结构
  • 只会比较同位置元素(默认)=>给元素添加key,则同层元素都会比较
  • 在比较过程中: 同类型元素做修改,不同类型元素重新创建

组件性能优化

父组件render被调用, 那么所有后代组件的render也会被调用, 类组件:创建组件时让组件继承于React.PureComponent 函数组件:通过高阶函数React.memo(),返回优化后的组件

const PurHome = React.memo(function() {
    return (
        <div>
            <p>Home</p>
        </div>
    )
});

获取元素

通过createRef()创建一个对象, 然后将这个对象传递给ref

constructor(props){
        super(props);
        this.oPRef = React.createRef();
}
<p ref={this.oPRef}>我是段落</p>
this.oPRef.current属性获取元素

通过传递一个回调函数, 然后保存回调函数参数的方式

constructor(props){
        super(props);
        this.oPRef = null;
}
<p ref={(arg)=>{this.oPRef = arg}}>我是段落</p>

注意点:

  • 获取原生元素, 拿到的是元素本身
  • 获取类组件元素, 拿到的是组件实例对象,获取函数组件元素, 拿不到任何内容

Ref转发:获取函数式组件内部元素

const About = React.forwardRef(function(props, myRef) { // myRef === this.myRef
    return (
        <div>
            <span ref={myRef}>我是span</span>
        </div>
    )
});
class App extends React.PureComponent{
    constructor(props){
        super(props);
        this.myRef = React.createRef();
    }
    render(){
        return (
            <div>
                <About ref={this.myRef}/>
            </div>
        )
    }
}

受控组件

表单元素内部形成一个闭环,值由内部state维护控制

React传送门

默认情况下, 所有的组件都是渲染到root元素中的,Portal提供了一种将组件渲染到其它元素中的能力


class Modal extends React.PureComponent{
    render() {
        /*
        this.props.children: 可以获取到当前组件所有的子元素或者子组件
        createPortal: 接收两个参数
        第一个参数: 需要渲染的内容
        第二个参数: 渲染到什么地方
        * */
       console.log(this.props);
        return ReactDOM.createPortal(this.props.children, document.getElementById('other'));
    }
}
class App extends React.PureComponent{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div id={'app'}>
                <Modal>
                    <div>123123</div>
                    <div>123123123</div>
                </Modal>
            </div>
        )
    }
}

空元素

React.Fragment

React.StrictMode

开启严格模式, 检查后代组件中是否存在潜在问题

  • 和Fragment一样, 不会渲染出任何UI元素
  • 仅在'开发模式'下有效

React样式

  • 内联样式优点:可以获取state中的状态,缺点需要使用驼峰,样式无提示

  • 外联样式 优点:编写简单, 有代码提示, 支持所有CSS语法,缺点:不可以动态获取当前state中的状态,默认属于css全局样式会相互影响

  • CSS模块化样式 文件命名:样式文件名.module.后缀名 优点:解决全局样式污染,缺点:不可以动态获取state

css-in-js

简介:用JS来编写CSS的库

styled-components使用
安装styled-components,npm install styled-components --save
导入styled-components,import styled from 'styled-components';
创建 StyleDiv组件并且设置子元素样式
const StyleDiv = styled.div`
    p{
        font-size: 50px;
        color: red;
    }
    a{
       font-size: 25px;
       color: green;
    }
`;

styled-components特性
1:props
const StyleDiv = styled.div`
    p{
        font-size: 50px;
        color: ${props => props.color};
    }
    a{
       font-size: 25px;
       color: green;
    }
`;
render() {
    return (
        <StyleDiv color={this.state.color}>
            <StyleInput></StyleInput>
        </StyleDiv>
    )
}
2:attrs
调用完attrs方法之后, 这个方法返回的还是一个函数
// 所以我们还可以继续通过字符串模板来调用
const StyleInput = styled.input.attrs({
    type:'password'
})``
3:设置主题
import {ThemeProvider} from 'styled-components'
<ThemeProvider theme={{size:'50px', color:'blue'}}></ThemeProvider>
4:继承
const BaseDiv = styled.div`
  font-size: 100px;
  background: blue;
`;
const StyleDiv1 = styled(BaseDiv)`
  color: red;
`;
const StyleDiv2 = styled(BaseDiv)`
  color: green;
`