React学习笔记

React 入门

环境搭建

前提:node 环境

脚手架:create-react-app

先使用官方脚手架来完成我们第一个 react 项目

安装及使用:

npm install create-react-app -g //全局安装脚手架
create-react-app react-demo  //初始化一个名为react-demo的项目
cd react-demo //进入项目
npm start  //启动项目

脚手架默认的是 3000 端口,启动完成便可以访问了

工程目录

index.js-项目入口文件
在这里插入图片描述

JSX 语法

使用 JSX 语法必须引入 react,JSX 语法不需要加引号。

//App.js
import React, { component } from "react";
class App extends Component {
  render() {
    //JSX语法
    return <div>111</div>;
  }
}
export default App;

JSX 语法最外层必须包裹一个元素,可用占位符<Fragment></Fragment>

事件和 vue 相似,小驼峰 eg: onChange/onClick

绑定的值需要被{}包裹起来,相当于{}内部是 js 语法,jsx 里面的注释也要放到{}里,与 js 单行多行注释相同

响应式设计:数据即界面,在 react 里改变数据需要用到 this.setSate(),因为 react 的 immutable 思想,后期也有优化空间

react 里会认为 class 是一个类,而非样式类名,react 的 JSX 中使用样式类时使用 className;会认为 label 标签的 for 是循环的 for,需要替换成 htmlFor

组件通信

父组件向子组件传值

属性传值:属性名自定义,在子组件中通过this.props.属性名来获取,

如果子组件需要与父组件通信,调用父组件的方法也是如此,如下图的 delete。

在这里插入图片描述

子组件向父组件传值

这里并不是真正意义上的给父组件传值,而是调用父组件里的方法。在 react 中是单向数据流,允许数据从父组件到子组件,子组件可以使用但不可更改父组件的数据,所以说 react 只是一个视图层框架,不负责数据相关事情。

React 特点

声明式开发:指定你要做什么,但怎么做不做说明(what to do);与之相对的,命令式开发,会说明步骤(how to do)。

可以与其他框架并存:

组件化、单向数据流、视图层框架(不负责数据流之类)、

函数式编程:面向测试编程

React 进阶

安装 react-devtools

2020/11 官网已经有 chrome 扩展程序的压缩包了,直接下载并拖到 chrome 扩展程序中就好了,是 react 开发的网页那图标会变成黑色。

详细步骤:安装react-devtools

在这里插入图片描述

(题外话:假如 chrome 扩展程序下载好之后是.crx 文件无法安装的话,可更改为 rar,解压之后在去扩展程序中,加载已解压的扩展程序)

propTypes 和 defaultProps

propTypes:类似 typescript , 用于对属性类型做校验

defaultProps:默认值

虚拟 dom

props、state 与 render() 之间的关系:

当组件的 props 或者 state 发生改变的时候,render 函数会执行;

当父组件的 render 函数执行的时候,子组件的 render 函数也会执行;

React 数据驱动的底层逻辑

  1. 数据 state
  2. 模板 JSX
  3. 数据 + 模板 (state + template)生成虚拟 dom(虚拟 dom 就是一个 js 对象)如:{'div',{id:'#root'},{'span',{},'hello world'}}
  4. 根据虚拟 dom 生成真实 dom<div id='root'><span>hello world</span></div>
  5. 数据 state 发生变化
  6. 数据 state+模板 JSX 生成新的虚拟 dom{'div',{id:'#root'},{'span',{},'hello'}}
  7. 用新的虚拟 dom 与原始虚拟 dom 进行比较,发现 span 内容不同
  8. 直接操作 dom,改变 span 中的值

优点:

  1. 极大的提升了性能
  2. 使得跨端应用得以实现 React Native

虚拟 dom 的 diff 算法

setState

setState 是异步的,假如在相隔较近的一段时间内多次调用 setState,那 setState 会整合之后调用一次。

diff 的同级比较

算法空间复杂度低,只比对一层 dom,某层不同则会替换从这一层开始往下的所有 dom

在这里插入图片描述

key 的作用

在这里插入图片描述

在比较过程中,会根据 key 值快速进行比较,所以 key 值需要是唯一且稳定的。因此在循环中不要使用 index 作为 key 值。
这里唯一指的是:数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。
然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值

ref

尽量不要使用 ref 来操作 dom,但如果有需要的话,需要在 state 回调函数中进行操作避免一些奇怪的 bug

//JSX

生命周期函数

生命周期函数指在某一时刻组件会自动调用执行

在这里插入图片描述

mount:挂载,指的是组件第一次被放到页面上的时候

在这里插入图片描述

React17中将废除componentWillMount、componentWillRecieveProps、componentWIllUpdate,因为这几个生命周期可能会被误用,比如 在componentWillMount里面去请求数据,就可能会造成空渲染,渲染两次等问题,所以最好的是请求放在 componentDidMount。

新增的生命周期:getDerivedStateFromProps

组件

纯函数指的是给定固定的输入,就一定又固定的输出,而且不会有任何副作用;
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

无状态组件/ 有状态组件

没有state不需要维护state就是无状态组件,无状态组件只需要根据外部传入的state来进行展示。

UI 组件/容器组件

按功能来分,只负责视图,不处理数据,就是UI组件

函数组件/类组件

按组件定义方式来分,函数式定义的组件,例如下面就是一个函数组件:

const TodoListUI = (props) => {
  return (
    <Fragment>
      <div>
        {/* 注释:TodoList-demo  label的作用:扩大点击热区,点击时光标会聚焦到对应input框 */}
        <Input 
          id="input" 
          value={props.inputValue} 
          onChange={props.inputChange}
          style={{width:300}}
        ></Input>
        <Button type="primary" onClick={props.submit}>提交</Button>
      </div>
      <ul>
        {/* { getItem() } */
          props.list.map((item, index) => {
            return (
              <TodoItem 
                key={index}
                content={item} 
                index={index} 
                deleteItem={props.delete.bind(this)}
              />
            )
          })
        }
        
      </ul>
    </Fragment>
  )
}

类组件,按照es6类的方式来定义的组件:

class TodoList extends Component {

  constructor(props) {
 	super(props);
  }

  render() {
    return (
        <TodoListUI/>
    );
  }

  componentDidMount() {}
}

函数组件一定是一个无状态组件,他没有内部的state,所以他的职责更明确;
类组件可以维护自己内部的state状态,内部有自己的生命周期,功能更强大。
如果组件简单不需要管理自身state就优先选择函数组件,代码简洁不繁琐,也符合react视图数据分离的口号。

react最近函数式编程+hooks呼声四起,接下来看看hooks吧

hook

hook的基本使用

React 性能

  1. 改变作用于的话,放在 constructor 里面绑定 this,只操作一次
  2. 虚拟 dom,diff 算法
  3. setState 异步
  4. shouldComponentUpdate
  5. 多使用无状态组件 直接 return 一个函数 没有组件实例,可以提升性能

Redux

Redux = Reducer + Flux

在这里插入图片描述

一个组件改变了 store 内的数据,别的组件会感知到,并且再次去取值。

Redux 工作流程

在这里插入图片描述

  1. 首先需要有 store, store 是唯一的,store 的数据只有 store 自己可以改变
// store/index.js
import { createStore } from "redux";

const store = createStore();
  1. 其次要有 reducer,管理 action,返回新的 state
// store/reducer.js
const defaultState = {};
export default (prevState = defaultState, action) => {
  //action对应的一些操作
  return state;
};
  1. store 与 reducer 建立连接
// store/index.js
import { createStore } from "redux";
import reducer from "./reducer";

const store = createStore(reducer);
  1. 组件中获取 store 的数据store.getState()并订阅 storestore.subscribe(func)

    (订阅:store 里的数据有变动会触发 func)

// src/todoList.js
import React, { component } from "react";
import store from "./store";
class todoList extends Component {
  constructor(props) {
    super(props);
    this.handle = this.handle.bind(this);
    this.handleStoreChange = this.handleStoreChange.bind(this);
    this.state = store.getState();
    store.subscribe(this.handleStoreChange);
  }
  render() {
    //...
  }
  handleStoreChange() {
    this.setState(store.getState());
  }
}
  1. 组件发起一个 action
// src/todoList.js
handle(e) {
    const action = {
        type: 'input_change',
        value: e.target.value
    }
    store.dispatch(action)
}
  1. reducer 处理 action 并返回 newState
// store/reducer.js
const defaultState = {};
export default (prevState = defaultState, action) => {
  //action对应的一些操作
  if (action.type === "input_change") {
    const newState = JSON.parse(JSON.stringify(prevState));
    newState.inputValue = action.value;
    return newState;
  }
  return prevState;
};
  1. 因为上面我们已经订阅了 store ,所以现在 store 的数据有变动组件会再去取 store 的新数据

提出 Action

提取出 action 可以避免 actionTypes 为字符串拼写错误导致的一系列问题。

ActionTypes

在 store 文件夹下新建 actionTypes.js 的文件夹,定义所需要的 actionTypes,组件和 reducer 引入 actionTypes 文件。

// store/actionTypes.js
export const CHANGE_INPUT_VALUE = "change_input_value";
export const SUBMIT_INPUT_VALUE = "submit_input_value";
export const DELETE_ITEM = "delete_item";
// src/todoList.js
import { CHANGE_INPUT_VALUE } from './store/actionTypes'

handle(e) {
    const action = {
        type: CHANGE_INPUT_VALUE,
        value: e.target.value
    }
    store.dispatch(action)
}

ActionCreator

// store/actionCreator.js

import { CHANGE_INPUT_VALUE } from "./actionTypes";

//返回一个对象
export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE,
  value: value,
});
// src/todoList.js
import { getInputChangeAction, submitInputAction, deleteItemAction } from './store/actionCreator'

handle(e) {
    const action = getInputChangeAction(value)
    store.dispatch(action)
}

中间件

中间件指的是 redux 的中间件,在 action — store 之间

redux-saga 和 redux-thunk 都是解决异步代码问题,拆分异步代码。

优点: 有助于做自动化测试、代码拆分管理

redux-thunk 使用

  1. 安装
npm install --save redux-thunk
  1. 引入 thunk 中间件
// store/index.js
// 安装引用中间件可以上官网查看
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer";

//使用redux-devtools和redux-thunk 两个中间件
const composeEnhancers =
  typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
        // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
      })
    : compose;

const enhancer = composeEnhancers(
  applyMiddleware(thunk)
  // other store enhancers if any
);
const store = createStore(reducer, enhancer);

export default store;
  1. 组件中就可以使用store.dispacth传递函数了
// src/todolist.js
import Redux, { component } from "redux";
import { getTodoList } from "./store/actionCreator.js";

class todoList extends Component {
  render() {
    // ...
  }
  componentDidMount() {
    const action = getTodoList();
    store.dispatch(action);
  }
}
  1. 将 axios 请求在 actionCreator 中定义好,如下:

    getTodoList 返回一个函数,函数的参数是 dispatch。

    发送请求并获取到数据后,生成一个 action,并触发 store.dispacth 告知 store 数据有变化,在进行下一步操作。

// store/actionCreator.js
import axios from "axios";
import { INIT_DATA } from "actionTypes";

const initData = (value) => ({
  type: "INIT_DATA",
  value: value,
});

export const getTodoList = () => {
  return (dispatch) => {
    axios.get("/api/todolist").then((res) => {
      const data = res.data;
      const action = initData(data);
      dispatch(action);
    });
  };
};

redux-saga 使用

  1. 安装
npm install --save redux-saga
  1. 引入 redux-saga 中间件
// store/index.js
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import reducer from "./reducer";
import todoSaga from "./sagas";

const sagaMiddleWare = createSagaMiddleWare();
const composeEnhancers =
  typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
        // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
      })
    : compose;

const enhancer = composeEnhancers(applyMiddleWare(sagaMiddleWare));

const store = createStore(reducer, enhancer);
sagaMiddleWare.run(todoSaga);

export default store;
  1. actionTypes 和 actionCreator
// store/actionTypes.js
export const INIT_TODO_LIST = "init_todo_list";
export const GET_INIT_DATA = "get_init_data";
// store/actionCreator.js
import { INIT_TODO_LIST, GET_INIT_DATA } from "actionTypes";
export const initTodoList = (value) => ({
  type: INIT_TODO_LIST,
  value: value,
});

export const getInitData = () => ({
  type: GET_INIT_DATA,
});
  1. saga 文件
// saga.js
import { takeEvery, put } from "redux-saga/effects";
import axios from "axios";

import { GET_INIT_DATA } from "./actionTypes";
import { initTodoList } from "./actionCreator";

function* getTodoList() {
  try {
    const res = yield axios.get("api/todolist");
    const action = initTodoList(res.data);
    yield put(action);
  } catch (e) {}
}

function* todoSaga() {
  yield takeEvery(GET_INIT_DATA, getTodoList);
}
  1. 组件中
// src/todolist.js
import Redux, { component } from "redux";
import { getInitData } from "./store/actionCreator.js";

class todoList extends Component {
  render() {
    // ...
  }
  componentDidMount() {
    const action = getInitData();
    store.dispatch(action);
  }
}

其他中间件

redux-log :日志


React-Redux

react-redux 是第三方的模块,能使开发者更好的在 react 中使用 redux

1.安装

npm install --save react-redux

2.入口文件引入 provider,提供者,可以把 store 的数据给到它所包含的所有组件

// index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";

import store from "./store";
import TodoList from "./TodoList";

ReactDOM.render(
  <Provider store={store}>
    <TodoList />
  </Provider>,
  document.getElementById("root")
);

3.TodoList.js 文件,使用 connect 去和 Todolist 做连接,不在需要导出 TodoList 组件,直接导出 connect

connect 方法接受四个参数 mapStateToProps(映射到 props)、mapDispatchToProps、mergeProps、(Todolist)(组件)

函数将被调用两次,一次是映射,一次是组件与 store 进行连接,生成新的 connector,并不会改变原始组件

import React, { Component } from 'react';
import { connect } from 'react-redux'
import { getInitData, getInputChangeAction, submitInputAction, deleteItemAction} from './store/actionCreator'
import TodoListUI from './TodoListUI'

class TodoList extends Component {
    render() {
        return (
        	return (
                <TodoListUI
                  inputValue={this.props.inputValue}
                  list={this.props.list}
                  inputChange={this.props.handleInputValue}
                  submit={this.props.handleSubmit}
                  delete={this.props.handleDelete}
                />
            );
        )
    }
}

const mapStateToProps = (state) => {
  return {
    inputValue: state.inputValue,
    list: state.list
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    handleInputValue: (e) => {
      const value = e.target.value
      const action = getInputChangeAction(value)
      dispatch(action)
    },
    handleSubmit: () => {
      const action = submitInputAction()
      dispatch(action)
    },
    handleDelete: (index) => {
      const action = deleteItemAction(index)
      dispatch(action)
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoList);



样式

react 动画

styled-components

第三方样式插件 styled-components,生成样式组件,相当于给样式加了命名空间,可避免全局样式污染

一个彩蛋:你还在写 reset.css 吗,看过来
https://meyerweb.com/eric/tools/css/reset/

实战遇到的问题

1. 引入图片的两种方式:

1.1 import 引入

import imgURL from "./../images/photo.png";
<img src={imgURL} />;

1.2 require 引入

<img src={require("./../images/photo.png")} />

推荐第一种方式引入,第一种在打包过程中,webpack 会认为是资源从而根据你的配置小于多少 kb 来进行压缩或者 base64 编码;

第二种方式引入,require 内必须是字符串,打包时也只会认为是字符串,不会进行优化,也可能出现找不到图片的问题。

2. styled-components 的安装使用

  npm install --save styled-components

2.1 基础用法

/* 创建了一个Wrapper样式组件,该组件渲染之后是一个div标签 */
const Wrapper = styled.div`
  color: blue;
`;

/* Wrapper组件跟其余的react组件一样,只不过现在他们有了自己的样式 */
render(<Wrapper>Hello World!</Wrapper>);

2.2 选择器

const Wrapper = styled.div`
  /* 应用于Wrapper组件本身和Wrapper组件里的所有html标签 */
  color: black;

  /* 应用于Wrapper组件里的h3标签 */
  h3 {
    color: red;
  }

  /* 应用于Wrapper组件里的className为blue的html标签 */
  .blue {
    color: blue;
  }
`;

render(
  <Wrapper>
    <p>黑色 p 标签 </p>
    <h3>红色 h3 标签</h3>
    <p className="blue">蓝色 p 标签</p>
  </Wrapper>
);

const Thing = styled.button`
  color: blue;

  ::before {
    content: "!!!";
  }

  :hover {
    color: red;
  }
`;

render(<Thing>Hello world!</Thing>);

const Thing = styled.div`
  /* 应用于className为blue的Thing组件 */
  &.blue {
    color: blue;
  }

  /* 应用于className为red的Thing组件里的所有子组件或者html标签 */
  .red {
    color: red;
  }
`;

render(
  <React.Fragment>
    <Thing className="blue">Thing组件</Thing>
    <Thing>
      <p className="red">p标签</p>
    </Thing>
  </React.Fragment>
);

const Thing = styled.div`
  /* 应用于紧邻Thing组件的下一个Thing组件 */
  & + & {
    color: red;
  }
`;

render(
  <React.Fragment>
    <Thing>第一个Thing组件</Thing>
    <Thing>第二个Thing组件</Thing>
  </React.Fragment>
);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值