美文网首页
React学习——TodoList

React学习——TodoList

作者: Bingo是谁 | 来源:发表于2019-01-25 10:54 被阅读0次

工程初始化

create-react-app todolist

cd todolist

yarn start

工程目录文件简介

src/index.js 入口文件

src/app.test.js 自动化测试

registerServiceWorker

PWA progressive web application

部署在https协议的服务器上

可实现客户端离线加载

public/manifest.json 可将应用存在桌面

{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [ // 快捷方式图标
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }
  ],
  "start_url": ".", // 打开的地址
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}

react中的组件

自定义组件大写开头

src/index.js

import React from 'react'; // 需要引入
import ReactDOM from 'react-dom';
import App from './App';

// JSX <App />
ReactDOM.render(<App />, document.getElementById('root'));

reactDOM 将组件挂载在dom节点下

src/app.js

import React, { Component } from 'react'; // React需要引入
class App extends Component {
  render() {
    // JSX
    return (
      <div>
        hello
      </div>
    );
  }
}

export default App;

React需要引入,因为使用了JSX语法

render函数必须返回根节点

react 16开始, render支持返回数组

import React from 'react';

export default function () {
    return [
        <div>一步 01</div>,
        <div>一步 02</div>,
        <div>一步 03</div>,
        <div>一步 04</div>
    ];
}

React 16为我们提供了Fragments。
Fragments与Vue.js的<template>功能类似,可做不可见的包裹元素。

import React, { Fragment } from 'react'

export default function () {
    return (
        <Fragment>
            <div>一步 01</div>
            <div>一步 02</div>
            <div>一步 03</div>
            <div>一步 04</div>
        </Fragment>
    );
}

实现todoLIst的增加删除功能

handleBtnClick() {
this.setState({
  list: [...this.state.list, this.state.inputValue],
    inputValue: ''
  })
}

handleItemDelete(index) {
  // immutable
  // state 不允许我们做任何改变
  const list = [...this.state.list]; // 拷贝list
  list.splice(index, 1);
  this.setState({
    list: list
  })
}

JSX语法细节补充

JSX写注释

花括号里面是js代码

<div>
  {/* 多行注释 */}
  {
    // 单行注释,必须换行
   }
  <input
    value={this.state.inputValue}
    onChange={this.handleInputChange.bind(this)}
  />
  <button onClick={this.handleBtnClick.bind(this)}>提交</button>
</div>

用className替代class,避免与ES6 class关键字冲突

import './style.css'

<input
  className='input'
  value={this.state.inputValue}
  onChange={this.handleInputChange.bind(this)}
/>

dangerouslySetInnerHTML的使用

为了防止XSS攻击,在react中,通过富文本编辑器进行操作后的内容,会保留原有的标签样式,并不能正确展示。

在显示时,将内容写入__html对象中即可。具体如下:

<div dangerouslySetInnerHTML = {{ __html: checkMessages.details }} />

如果是直接调用接口中的值,则是以上的写法,如果是单纯的显示固定的内容,用如下的写法:

<div dangerouslySetInnerHTML={{ __html: '<div>123</div>' }} />

用htmlFor代替label标签的for

<div>
  <label htmlFor="inserArea">输入内容</label>
  <input
    id='inserArea'
  />
  <button onClick={this.handleBtnClick.bind(this)}>提交</button>
</div>

组件间通过props传值

// Todolist.js
return (
  <div key={index}>
    <ToDoItem
      content={item}
      index={index}
      deleteItem={this.handleItemDelete}
    />
</div>
)

// TodoItem.js
render() {
  const { content } = this.props;
  return (
    <div onClick={this.handleClick}>
      {content}
    </div>
  )
}

handleClick() {
  const { deleteItem, index } = this.props;
  deleteItem(index);
}

TodoList代码优化

在constructor中绑定this,提高性能

constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this)
}

代码拆分

// 使JSX更简洁
<ul>
  {this.getTodoItem()}
</ul>

getTodoItem() {
  return this.state.list.map((item, index) => {
    return (
      <ToDoItem
        key={index}
        content={item}
        index={index}
        deleteItem={this.handleItemDelete}
      />
    )
  })
}

异步更新状态

handleInputChange(e) {
  const value = e.target.value;
  // 用函数代替对象
  this.setState(() => ({
    inputValue: value
  }))
}

该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数

handleBtnClick() {
  this.setState((prevState, props) => ({
    list: [...prevState.list, prevState.inputValue],
    inputValue: ''
  }))
}

围绕react衍生出的思考

1.声明式开发

节约大量DOM操作代码

相对于命令式开发 jQuery操作DOM

2.可以与其它框架并存

react只管理一个DOM

3.组件化

4.单向数据流

子组件不能修改父组件传过来的值

只能调用父组件的方法修改数据

5.视图层框架

6.函数式编程

为自动化测试提供了方便

PropTypes与DefaultProps的应用

首先,引入prop-types包(脚手架自带)

import ProTypes from 'prop-types'

ToDoItem.propTypes = {
  test: PropTypes.string.isRequired,
  content: PropTypes.string,
  deleteItem: PropTypes.func,
  index: PropTypes.number
}

ToDoItem.defaultProps = {
  test: 'todo'
}

props,state与render函数的关系

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

React中的虚拟DOM

  1. state 数据
  2. JSX 模板
  3. 数据 + 模板 结合,生成真实的DOM,来显示
  4. state 发生改变
  5. 数据 + 模板 结合,生成真实的DOM,替换原始的DOM

缺陷:

第一次生成了一个完整的DOM片段
第二次生成了一个完整的DOM片段
第二次的DOM替换第一次的DOM,非常耗性能\

  1. state 数据
  2. JSX 模板
  3. 数据 + 模板 结合,生成真实的DOM,来显示
  4. state 发生改变
  5. 数据 + 模板 结合,生成真实的DOM,并不直接替换替换原始的DOM
  6. 新的DOM和原始的DOM作比对,找差异
  7. 找出input框发生了变化
  8. 只用新的DOM中的input元素,替换掉老的DOM中的input元素

缺陷:
性能提升不明显

  1. state 数据

  2. JSX 模板

  3. 生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实的DOM)(损耗了性能)
    ['div', {id: 'abc'}, ['span', {}, 'hello world']]

  4. 数据 + 模板 结合,生成真实的DOM,来显示
    <div id="abc"><span>hello world</span></div>

  5. state 发生改变

  6. 数据 + 模板 生成新的虚拟DOM (极大的提升了性能)
    ['div', {id: 'abc'}, ['span', {}, 'btyh']]

  7. 对比原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中的内容

  8. 直接操作DOM,改变span中的内容

优点:

  1. 性能提升了
  2. 它使得跨端应用得以实现。 React Native

深入了解虚拟DOM

// JSX -> createElement -> JS对象 -> 真实的DOM
return <div>item</div>
// 相当于
return React.createElement('div', {}, 'item')

虚拟DOM中的Diff算法

Diff difference
把多次setState结合成一次setState,减少虚拟DOM比对的次数
Diff算法会逐层比对,如果一层不满足匹配就不会继续往下比对,用新的替换老的\

React中ref的使用

采用回调 Refs的方式

<input
  id='inserArea'
  className='input'
  value={this.state.inputValue}
  onChange={this.handleInputChange}
  ref={(input) => {this.input = input}}
/>

可以直接访问DOM元素

handleInputChange(e) {
  // const value = e.target.value;
  const value = this.input.value;
  this.setState(() => ({
    inputValue: value
  }))
}

尽量避免使用ref

React的生命周期函数

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

1.Initialization 组件初始化阶段
setup props and state 在constructor中执行

2.Mounting 组件第一次挂载阶段
componentWillMount -> render -> componentDidMount

3.Updation 组件更新
props改变 -> componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

state改变 -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

shouldComponentUpdate如返回false后面不再执行

componentWillReceiveProps执行条件:1.一个组件要从父组件接收参数 2.父组件的render函数被重新执行

4.Unmounting 组件卸载
componentWillUnmount

生命周期函数的使用场景

  1. 父组件render函数重新执行,子组件render函数也会重新执行,这样会造成性能浪费

    解决方法:

    // 在子组件TodoItem中
    shouldComponentUpdate(nextProps, nextState) {
      if (nextProps.content !== this.props.content) {
        return true;
      } else {
        return false;
      }
    }
    
  2. 在componentDidMount执行Ajax数据请求,因为componentDidMount只执行一次

使用react-transition-group实现动画

Redux

Redux = Reducer + Flux

工作流程

Redux工作流程

Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供createStore这个函数,用来生成 Store。

import { createStore } from 'redux';
const store = createStore(reducer);

State

Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。

当前时刻的 State,可以通过store.getState()拿到。

import { createStore } from 'redux';
const store = createStore(reducer);

const state = store.getState();

Action

Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置。

{type: ”ADD”, key1: ”“,key2: ”“}

Action Creator

View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

const ADD_TODO = '添加 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');

Reducer

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

const reducer = function (state, action) {
  // ...
  return new_state;
};

每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

为什么这个函数叫做 Reducer 呢?因为它可以作为数组的reduce方法的参数。请看下面的例子,一系列 Action 对象按照顺序作为一个数组。

const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];

const total = actions.reduce(reducer, 0); // 3

三个基本原则

  1. store必须是唯一的\
  2. 只有store能够改变自己的内容\
  3. Reducer必须是纯函数

纯函数指的是,给固定的输入,就一定会有固定的输出。而且不会有任何副作用

数据流

1.调用 store.dispatch(action)

2.Redux store 调用传入的 reducer 函数。

3.根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树

4.Redux store 保存了根 reducer 返回的完整 state 树。

Redux核心API

createStore
store.dispatch
store.getState
store.subscribe

redux-thunk中间件

让store.dispatch方法可接收函数
步骤:
1.yarn add redux-thunk 安装
2.与REDUX_DEVTOOLS一起使用

import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

const enhancer = composeEnhancers(
  applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);

export default store

3.把异步操作移到actionCreator

export const getTodoList = () => {
  return (dispatch) => {
    axios.get('/list.json').then((res) => {
      const data = res.data;
      const action = initListAction(data);
      dispatch(action);
    })
  }
}

4.在组件中提交action

componentDidMount() {
    const action = getTodoList();
    store.dispatch(action);
  }

中间件

action 跟reducer的中间,对dispatch方法的包装升级
applyMiddleware源码

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

React-redux的使用

Provider组件
connect 连接组件和store
mapStateToProps 组件和store的映射
mapDispatchToProps store.dispatch挂载到props

相关文章

网友评论

      本文标题:React学习——TodoList

      本文链接:https://www.haomeiwen.com/subject/urytrqtx.html