Web全栈架构师(二)——React学习笔记(1)

起步

资源

安装脚手架

npm install -g create-react-app
  • react的api比较少,基本学一次,就再也不用看文档了,核心就是 js 的功力

快速创建项目

  • 创建项目的命令:create-react-app react01
  • cd react01
  • 启动命令:npm start

文件结构

|—— README.md					文档
|—— package-lock.json			
|—— package.json				npm依赖
|—— public						静态资源
|	|—— favicon.ico
|	|—— index.html
|	|—— manifest.json
|—— src							源码
	|—— App.css	
	|—— App.js					根组件
	|—— App.test.js				测试
	|—— index.css				全局样式
	|—— index.js				入口
	|—— logo.svg	
	|—— serviceWorker.js		pwa支持

React 和 ReactDOM

  • 删除src下面所有代码文件,新建index.js(项目入口文件)
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'

ReactDOM.render(<App/>, document.querySelector('#root'));
  • 新建App.js(根组件)
import React, { Component } from 'react'

class App extends Component{
	render(){
		return (
		    <div>
		      Hello, React
		    </div>
	    )
	}
}
export default App;
  • React 设计之初,就是使用 JSX 来描述 UI,所以解耦 和 DOM 操作,React 只做逻辑层,ReactDOM去渲染实际的DOM,如果换到移动端,就使用其他的渲染库。

JSX

  • 上面的代码会有一些困惑的地方,首先就是 JSX 的语法ReactDOM.render(<App/>, document.querySelector('#root'));,看起来就是 js 和 HTML的混合体,被称之为 JSX,实际核心逻辑完全是 js 实现的。
  • JSX实质就是 React.createElement 的调用:
    在这里插入图片描述
    在这里插入图片描述

state 和 setState

  • 在App组件里,我们可以通过 {} 在JSX中渲染变量
import React, { Component } from 'react'

export default class App extends Component{
	render(){
		const name = 'React';
		return (
		    <div>
		      Hello, {name}
		    </div>
	    )
	}
}
  • 如果数据需要修改,并且同时页面响应变化,我们需要放在 state 中,并且使用 setState 来修改数据
import React, { Component } from 'react'

export default class App extends Component{
	constructor(props){
		super(props);
		this.state = {
			msg = 'React';
		}
		setTimeout(() => {
			this.setState({
				msg: 'React is very nice!'
			});
		}, 2000);
	}
	render(){
		return (
		    <div>
		      {this.state.msg}
		    </div>
	    )
	}
}
  • 注意:我们不能使用this.state.msg='xxxxx'直接对 state 中维护的数据进行修改,而是要使用 this.setSate 去设置一个新的 state,对数据进行覆盖。

props属性传递

  • index.js
ReactDOM.render(<App title="React 学习"/>, document.querySelector('#root'));
  • App.js
...
<h2>{this.props.title}</h2>

条件渲染和循环

  • React 的 api 不多,条件渲染和循环,都是普通的 js 语法
export default class CartSample extends Component {
  constructor(props){
    super(props);
    this.state = {
      goods: [
        {id: 1, text: 'Web全栈架构师', price: 666},
        {id: 2, text: 'React', price: 888},
        {id: 3, text: 'Vue', price: 888}
      ]
    }
  }
  render() {
    const title = this.props.title ? <h1>{this.props.title}</h1> : null;
    // 循环:将js对象数组转换为jsx数组
    const goods = this.state.goods.map(good => (
      <li key={good.id}>{good.text}
        <button onClick={() => this.addToCart(good)}>加购</button>
      </li>
    ));
    return (
      <div>
        {/* 条件语句 */}
        {/* {this.props.title && <h1>{this.props.title}</h1>} */}
        {title}

        {/* 列表渲染 */}
        <ul>{goods}</ul>
      </div>
    )
  }
}

class VS 函数组件

  • 如果一个组件只根据 props 渲染页面,没有内部的 state(即组件是无状态的),我们完全可以用函数组件的形式来实现hooks的到来会改变这个现状)
function Title(title){
	return <h2>{title}</h2>
}
<Title title={this.props.title}></Title>

事件监听

  • React 中使用 onClick 类似的写法来监听事件,注意 this 绑定问题 react 里严格遵循单向数据流,没有数据双向绑定,所以输入框要设置 value 和 onChange事件
handleChange(e){
	this.setState({
		name: e.target.value
	});
}
// 写法1:箭头函数自动修正 this
<input type="text" value={this.state.name} onChange={(e) => this.handleChange(e)}/>
// 写法2:需要在构造函数里手动绑定this,否则会报错
<input type="text" value={this.state.name} onChange={this.handleChange}/>
constructor(props){
	super(props);
	...
	this.handleChange = this.handleChange.bind(this);
}
// 写法3:
handleChange = (e) => {
	this.setState({name: e.target.value})
}

组件通信

  • 购物车组件:
// 购物车组件 : 函数型组件
function Cart(props){
  return (
    <table>
      <tbody>
        {props.data.map(d => (
          <tr key={d.id}>
            <td>{d.text}</td>
            <td>
            <button onClick={() => props.descCount(d)}>-</button>
            {d.count}
            <button onClick={() => props.incrCount(d)}>+</button>
            </td>
            <td>{d.price * d.count}</td>
            <td></td>
          </tr>
        ))}
      </tbody>
    </table>
  )
}
  • 简易购物车组件:
export default class CartSample extends Component {
  constructor(props){
    super(props);
    this.state = {
      goods: [
        {id: 1, text: 'Web全栈架构师', price: 666},
        {id: 2, text: 'React', price: 888},
        {id: 3, text: 'Vue', price: 888}
      ],
      inputText: '', //  输入的商品名
      cart: []
    }
    // // 回调的写法1
    // this.addGood = this.addGood.bind(this);
  }
  // addGood(){
  //   // TODO
  // }
  // 回调的写法2
  addGood = () => {
    this.setState(prevState => ({
      goods: [...prevState.goods, {id: 4, text: prevState.inputText, price: 999}]
    }));
  }
  textChangeHandle = (ev) => {
    this.setState({
      inputText: ev.target.value
    });
  }
  addToCart(good){
    const newCart = [...this.state.cart];
    const idx = newCart.findIndex(c => c.text === good.text);
    const item = newCart[idx];
    if(item){
      newCart.splice(idx, 1, {...item, count: item.count+1});
    } else {
      newCart.push({...good, count: 1});
    }
    this.setState({cart: newCart});
  }
  incrCount = (item) => {
    const newCart = [...this.state.cart];
    const idx = newCart.findIndex(c => c.text === item.text);
    newCart.splice(idx, 1, {...item, count: item.count+1});
    this.setState({cart: newCart});
  }
  descCount = (item) => {
    const newCart = [...this.state.cart];
    const idx = newCart.findIndex(c => c.text === item.text);
    // newCart.splice(idx, 1, {...item, count: item.count-1});
    if(item.count === 1){
      newCart.splice(idx);
    }else{
      newCart.splice(idx, 1, {...item, count: item.count-1});
    }
    this.setState({cart: newCart});
  }
  render() {
    const title = this.props.title ? <h1>{this.props.title}</h1> : null;
    // 循环:将js对象数组转换为jsx数组
    const goods = this.state.goods.map(good => (
      <li key={good.id}>{good.text}
        <button onClick={() => this.addToCart(good)}>加购</button>
      </li>
    ));
    return (
      <div>
        {/* 条件语句 */}
        {/* {this.props.title && <h1>{this.props.title}</h1>} */}
        {title}

        {/* 添加商品 */}
        <div>
          <input type="text" value={this.state.inputText} onChange={(e) => this.textChangeHandle(e)}/>
          <button onClick={this.addGood}>添加商品</button>          
        </div>

        {/* 列表渲染 */}
        <ul>{goods}</ul>

        {/* 购物车组件 */}
        <Cart data={this.state.cart} incrCount={this.incrCount} descCount={this.descCount}/>
      </div>
    )
  }
}

虚拟DOM

  • 浏览器渲染图
    在这里插入图片描述
  • 浏览器的渲染过程主要包括以下几步:
* 解析HTML生成DOM树。
* 解析CSS生成CSSOM规则树。
* 将DOM树与CSSOM规则树合并在一起生成渲染树。
* 遍历渲染树开始布局,计算每个节点的位置大小信息。
* 将渲染树每个节点绘制到屏幕。
  • 实际的DOM节点很重:
    在这里插入图片描述
  • DOM操作成本实在是太高,所以才有了在js里模拟和对比,JSX里使用 react,createElement构建虚拟DOM。每次有修改,先对比 js 里的虚拟DOM树:
    在这里插入图片描述

生命周期

  • React 生命周期分为三种状态:初始化、更新、销毁
    在这里插入图片描述

初始化

  • getDefaultProps():设置默认的props,也可以用defaultProps设置组件的默认属性.
  • getInitialState():在使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state,此时可以访问this.props
  • componentWillMount():组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。
  • render():react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
  • componentDidMount():组件渲染之后调用,只调用一次。

更新

  • componentWillReceiveProps(nextProps):组件初始化时不调用,组件接受新的props时调用。
  • shouldComponentUpdate(nextProps, nextState):react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候
  • componentWillUpdata(nextProps, nextState):组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state
  • render():组件渲染
  • componentDidUpdate():组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点。

卸载

  • componentWillUnmount():组件将要卸载时调用,一些事件监听和定时器需要在此时清除。

演示代码

  • index.js
ReactDOM.render(<Lifecycle/>, document.querySelector('#root'));
  • Lifecycle.js
import React, { Component } from 'react'

class Lifecycle extends Component {
  constructor(props){
    super(props);
    console.log('1. 构造函数');
    this.state = {
      mag: '来自属性传递:' + props.prop
    }
  }
  componentWillMount(){
    // 此时可以访问属性和状态了,可以进行api调用,但没办法做DOM相关操作
    console.log('2. 组件将要挂载');
  }
  componentDidMount(){
    // 组件已挂载,可进行状态更新状态
    console.log('3. 组件已经挂载');
  }
  componentWillReceiveProps(){
    // 父组件传递的属性有变化,做相应的响应
    console.log('4. 组件属性更新了');
  }
  shouldComponentUpdate(){
    // 组件是否需要更新,返回布尔值,优化点
    console.log('5. 组件是否应该更新');
    return true
  }
  componentWillUpdate(){
    console.log('6. 组件将要更新');
  }
  componentDidUpdate(){
    console.log('7. 组件已经更新');
  }
  render() {
    console.log('组件渲染');
    return (
      <div>
        React 组件生命周期探究
      </div>
    )
  }
}
export default class extends Component {
  constructor(props){
    super(props);
    this.state = {
      someProp: 'some prop'
    }
    setTimeout(() => {
      this.setState({
        someProp: 'a new prop'
      })
    }, 2000);
  }
  render() {
    return (
      <Lifecycle prop={this.state.someProp}/>
    )
  }
}

组件化

React没有Vue那么多的API,基本全部都是组件,React的开发模式,大体可以用一个公式表达

UI = F(state)

ant-design组件库

试用 ant-design组件库

  • ant-design组件库网址:https://ant.design/docs/react/use-with-create-react-app-cn
  • 安装:
npm install antd --save
  • 试用:
import React, { Component } from 'react'
import Button from 'antd/lib/button'
import 'antd/dist/antd.css'

class App extends Component{
	render(){
		return (
			<div>
				{/* antd的试用 */}
		        <Button type="primary">button</Button>
			</div>
		)
	}
}

配置按需加载

  • 安装 react-app-rewired 取代 react-scripts,可以扩展 webpack 的配置,类似于 vue.config.js
    npm install react-app-rewired@2.0.2-next.0 babel-plugin-import --save
  • 修改 package.json
"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-app-rewired eject"
}
  • 项目根目录新建文件 config-overrides.js
const {injectBabelPlugin} = require('react-app-rewired')

module.exports = function override(config, env){
  config = injectBabelPlugin([
    'import', {libraryName: 'antd', libraryDirectory: 'es', style: 'css'}
  ], config);
  return config;
}
  • 接下来就可以在组件中导入了:
import {Button} from 'antd' // 具名导入

容器组件 vs 展示组件

  • 基本原则:容器组件负责数据获取,展示组件负责根据 props 显示信息
  • 优势:
* 如何工作(业务逻辑)和如何展示分离
* 重用性高
* 更高的可用性
* 更易于测试
// 容器
export default class CommentList extends Component {
  constructor(props){
    super(props);
    this.state = {
      comments: []
    }
  }
  componentDidMount(){
    setInterval(() => {
      this.setState({
        comments: [
          {body: 'react is very good', author: 'facebook'},
          {body: 'vue is very good', author: 'youyuxi'}
        ]
      })
    }, 1000);
  }
  render() {
    return (
      <div>
        {this.state.comments.map((c, i) => (
          <Comment key={i} data={c}></Comment>
        ))}
      </div>
    )
  }
}
// 展示组件
function Comment({data}){
  console.log('render');
  return (
    <div>
      <p>{data.body}</p>
      <p>--{data.author}</p>
    </div>
  )
}
  • 问题:每隔1秒,不论数据有没有发生变化,Comment组件都会渲染,如何解决?
  • 我们可以想到利用生命周期函数 shouldComponentUpdate 判断数据是否发生变化,从而控制是否重新渲染
class Comment extends Component{
  shouldComponentUpdate(nextProps){
    if(nextProps.data.body === this.props.data.body &&
      nextProps.data.author === this.props.data.author){
      return false;
    }
    return true;
  }
  render(){
    console.log('render');
    return (
      <div>
        <p>{this.props.data.body}</p>
        <p>--{this.props.data.author}</p>
      </div>
    )
  }
}
  • 这样确实是可以解决组件重复渲染的问题,但是在 Comment 中的shouldComponentUpdate中写一大段的逻辑,显然不是我们所希望的,有没有更好的办法?
  • 回答是:YES,PureComponent(纯组件)

PureComponent

  • PureComponent定制了 shouldComponentUpdate 后的 Component 浅比较
class Comment extends PureComponent{
  render(){
    console.log('render');
    return (
      <div>
        <p>{this.props.body}</p>
        <p>--{this.props.author}</p>
      </div>
    )
  }
}
// 传值改变
<Comment key={i} body={c.body} author={c.author}></Comment>
  • 缺点就是必须使用 class 形式
    在这里插入图片描述
    在这里插入图片描述
  • 想要利用 PureComponent 特性,原则:
* 确保数据类型是值类型
* 果是引用类型,确保地址不变,同时不应当有深层次数据变化

React.memo

  • React v16.6.0之后的版本,可以使用一个新功能 React.memo来完美实现 React 组件,让函数式的组件也有了PureComponent的功能
  • 我们继续改造 Comment 组件:
// memo只是让函数组件可以拥有PureComponent的功能
const Comment = React.memo(({body, author}) => {
  console.log('render');
  return (
    <div>
      <p>{body}</p>
      <p>--{author}</p>
    </div>
  )
});
  • 父组件解构传参:
<Comment key={i} {...c}></Comment>

组件复合

  • 代码:
import React, { Component } from 'react'

// Dialog
function Dialog(props){
  return (
    <div style={{border: `4px solid ${props.color || 'blue'}`}}>
      {/* 等效于Vue中匿名插槽 */}
      {props.children}
      {/* 等效于Vue中具名插槽 */}
      <div className="footer">{props.footer}</div>
    </div>
  )
}
function WelcomeDialog(){
  const confirmBtn = <button onClick={() => alert('React确实好!')}>确定</button>
  return (
    <Dialog color="green" footer={confirmBtn}>
      <h1>欢迎光临</h1>
      <p>感谢使用React!!!</p>
    </Dialog>
  )
}

export default class Composition extends Component {
  render() {
    return (
      <div>
        <WelcomeDialog></WelcomeDialog>
      </div>
    )
  }
}
  • 问题1:children到底是什么?
    答:是任意合法的 JS 表达式,包括函数,例如:
// 模拟接口
const api = {
  getUser: () => ({name: 'jerry', age: 20})
}
function Fetcher(props){
  let user = api[props.name]();
  return props.children(user);
}
export default class Composition extends Component {
  render() {
    return (
      <div>
        {/* children内容可以是任意的表达式 */}
        <Fetcher name="getUser">
          {({name, age}) => (<p>{name} - {age}</p>)}
        </Fetcher>
      </div>
    )
  }
}
  • 问题2:children可以修改吗?
    (1)可以修改的情况,例如:
function FilterP(props){
  return  (
    <div>
      {/* React.Children:提供了若干操作嵌套内容的帮助方法 */}
      {React.Children.map(props.children, child => {
        console.log(child); // vdom
        if(child.type != 'p'){// 过滤掉非 p 标签
          return;
        }
        return child;
      })}
    </div>
  )
}
export default class Composition extends Component {
  render() {
    return (
      <div>
        {/* 操作children,过滤掉非 p 标签 */}
        <FilterP>
          <div>div标签</div>
          <h3>h3标签</h3>
          <p>p标签</p>
          <a>a标签</a>
        </FilterP>
      </div>
    )
  }
}

(2)不可以修改的情况,例如:

function RadioGroup(props){
  return (
    <div>
      {React.Children.forEach(props.children, child => {
        child.props.name = props.name;
      })}
    </div>
  )
}
function Radio({children, ...rest}){
  return (
    <label>
      <input type="radio" {...rest}/>
      {children}
    </label>
  )
}
export default class Composition extends Component {
  render() {
    return (
      <div>
        {/* 编辑children */}
        <RadioGroup name="mvvm">
          <Radio value="vue">vue</Radio>
          <Radio value="react">react</Radio>
          <Radio value="angular">angular</Radio>
        </RadioGroup>
      </div>
    )
  }
}

当我们试图给children增加一个属性 name 的时候,会报错,如下图:
在这里插入图片描述
那么我们应该怎么做?回答是,我们可以利用 map 方法返回一个全新的:

function RadioGroup(props){
  return (
    <div>
      {React.Children.map(props.children, child => {
        return React.cloneElement(child, {name: props.name});
      })}
    </div>
  )
}

高阶组件

  • 为了进一步提高组件的复用性,在React里就有了HOC(Higher-Order Components)的概念。高阶组件也是一个组件,但是她返回另一个组件,产生新的组件可以对属性进行包装,也可以重写部分生命周期。
  • 示例:
// 高阶组件
const withName = Comp => {
  // 写法一
  // 假设通过某种特殊手段获取了本节课名字
  // return props => <Comp {...props} name="高阶组件使用介绍"/>
  
  // 写法二
  // 甚至可以重写组件的生命周期
  class NewComponent extends Component {
    componentDidMount(){
      console.log('do something');
    }
    render() {
      return <Comp {...this.props} name="高阶组件使用介绍"/>
    }
  }  
  return NewComponent
}
  • 上面的 withName 组件,其实就是代理了Comp,只是多传递了一个 name 参数。

高阶链式调用

  • 高阶组件最巧妙的一点,是可以进行链式调用。假设我们再定义了高阶组件 withLog:
const withLog = Comp => {
  console.log(Comp.name + '渲染了');
  return props => <Comp {...props}/>
}
  • 我们要对 Kaikeba 这个组件功能进行增强,然后导出 Kaikeba 组件,就可以这样做:
function Kaikeba(props){
  return (
    <div>{props.stage} - {props.name}</div>
  )
}
export default withLog(withName(Kaikeba));
  • 问题:这种链式的写法显然不够优雅,有没有更好的方式呢?

高阶组件装饰器写法

  • ES7有一个优秀的语法——装饰器,专门用来处理上面这种问题
  • 安装:npm install --save-dev babel-plugin-transform-decorators-legacy
  • 配置:
const { injectBabelPlugin } = require("react-app-rewired");

module.exports = function override(config, env) {
  // antd的按需加载功能
  config = injectBabelPlugin(
    ["import", { libraryName: "antd", libraryDirectory: "es", style: "css" }],
    config
  );

  // 添加装饰器的功能
  config = injectBabelPlugin(
    ["@babel/plugin-proposal-decorators", { legacy: true }],
    config
  );

  return config;
};
  • 使用:
@withLog
@withName
@withLog
class Kaikeba extends Component{
  render() {
    return (
      <div>{this.props.stage} - {this.props.name}</div>
    )
  }
}
export default Kaikeba;

组件通信–上下文

  • Vue 中的 privide & inject 模式的来源——context
  • 这种模式下有两个角色,一个是 Provider,一个是 Consumer,Provider 是在外层的组件,内部需要数据的,就用 Consumer 来读取

老版本的上下文

  • getChildContext 用来返回数据
  • 定义 childContextTypes 声明

新版本的上下文

示例
import React, { Component } from 'react'

// 1. 创建上下文
const Context = React.createContext();

const store = {
  name: '开课吧',
  sayHi(){
    console.log(this.name);
  }
}

export default class ContextSample extends Component {
  render() {
    return (
      // 2. Provider提供数据 store
      <Context.Provider value={store}>
        <div>
          {/* 3. Consumer获取数据 */}
          <Context.Consumer>
            {/* 4. 必须内嵌一个函数 */}
            {value => <div onClick={() => value.sayHi()}>{value.name}</div>}
          </Context.Consumer>
        </div>
      </Context.Provider>
    )
  }
}
高阶组件的写法:
// 供应者
const withProvider = Comp => props => (
  <Context.Provider value={store}>
    <Comp {...props}></Comp>
  </Context.Provider>
)
// 消费者
const withConsumer = Comp => props => (
  <Context.Consumer value={store}>
    {/* 必须内嵌一个函数 */}
    {value => <Comp {...props} value={value}/>}
  </Context.Consumer>
)
// 内嵌到Consumer中的函数
@withConsumer
class Inner extends Component {
  render(){
    return (
      <div>{this.props.value.name}</div>
    )
  }
}
// ContextSample
@withProvider
class ContextSample extends Component {
  render() {
    return (
      <div>
        <Inner></Inner>
      </div>
    )
  }
}
export default ContextSample
createContext实现原理
function createContext(){
	let instance = {
		value: null
	}
	class Provider extends React.Component{
		constructor(props){
			super(props);
			instance.value = props.value;
		}
		render() {
			return this.props.children;
		}
	}
	class Consumer extends React.Component{
		constructor(props){
			super(props);
			this.state = {
				value: instance.value
			}
		}
		render() {
			return this.props.children(this.state.value);
		}
	}
	return {Provider, Comsumer};
}

自定义表单组件

试用antd的Form表单

import React from 'react'
import { Form, Icon, Input, Button } from "antd";

function hasErrors(fieldsError) {
  return Object.keys(fieldsError).some(field => fieldsError[field]);
}
class HorizontalLoginForm extends React.Component {
  componentDidMount() {
    // To disabled submit button at the beginning.
    this.props.form.validateFields();
  }
  handleSubmit = e => {
    e.preventDefault();
    this.props.form.validateFields((err, values) => {
      if (!err) {
        console.log("Received values of form: ", values);
      }
    });
  };
  render() {
    const {
      getFieldDecorator,
      getFieldsError,
      getFieldError,
      isFieldTouched
    } = this.props.form;
    // Only show error after a field is touched.
    const userNameError =
      isFieldTouched("userName") && getFieldError("userName");
    const passwordError =
      isFieldTouched("password") && getFieldError("password");
    return (
      <Form layout="inline" onSubmit={this.handleSubmit}>
        <Form.Item
          validateStatus={userNameError ? "error" : ""}
          help={userNameError || ""}
        >
          {getFieldDecorator("userName", {
            rules: [{ required: true, message: "Please input your username!" }]
          })(
            <Input
              prefix={<Icon type="user" style={{ color: "rgba(0,0,0,.25)" }} />}
              placeholder="Username"
            />
          )}
        </Form.Item>
        <Form.Item
          validateStatus={passwordError ? "error" : ""}
          help={passwordError || ""}
        >
          {getFieldDecorator("password", {
            rules: [{ required: true, message: "Please input your Password!" }]
          })(
            <Input
              prefix={<Icon type="lock" style={{ color: "rgba(0,0,0,.25)" }} />}
              type="password"
              placeholder="Password"
            />
          )}
        </Form.Item>
        <Form.Item>
          <Button
            type="primary"
            htmlType="submit"
            disabled={hasErrors(getFieldsError())}
          >
            Log in
          </Button>
        </Form.Item>
      </Form>
    );
  }
}
const WrappedHorizontalLoginForm = Form.create({ name: "horizontal_login" })(
  HorizontalLoginForm
);
// ReactDOM.render(<WrappedHorizontalLoginForm />, mountNode);
export default WrappedHorizontalLoginForm;

KFormSample

  • kFormCreate:包装用户表单,增加数据管理能力、校验
function kFormCreate(Comp) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.options = {}; // 字段选项设置
      this.state = {}; // 各字段的值
    }
    // 处理表单项输入事件
    handleChange = e => {
      const { name, value } = e.target;
      this.setState(
        {
          [name]: value // 这是异步的
        },
        () => {
          // 数值变化后再校验
          this.validateField(name);
        }
      );
    };
    // 处理表单项焦点事件
    handleFocus = e => {
      const field = e.target.name;
      this.setState({
        [field + "Focus"]: true
      });
    };
    // 表单校验
    validateField = field => {
      const rules = this.options[field].rules;
      // 校验结果,只有任何一项失败,就失败
      const result = rules.some(rule => {
        if (rule.required) {
          // 仅校验必填项
          if (!this.state[field]) {
            // 校验失败
            this.setState({
              [field + "Message"]: rule.message
            });
            return true; // 若有校验失败,返回true
          }
        }
      });
      if (!result) {
        // 没失败
        this.setState({
          [field + "Message"]: ""
        });
      }
      return !result;
    };
    // 提交表单时,校验所有字段
    validate = cb => {
      const results = Object.keys(this.options).map(field =>
        this.validateField(field)
      );
      // 如果校验结果数组中全部为true,则校验成功
      const result = results.every(v => v === true);
      // 回调校验结果
      cb(result);
    };
    //
    getFieldDec = (field, option, InputComp) => {
      this.options[field] = option;
      return (
        <div>
          {React.cloneElement(InputComp, {
            name: field, // 控件name
            value: this.state[field] || "",
            onChange: this.handleChange, // change事件处理
            onFocus: this.handleFocus // 判断控件是否获得焦点
          })}
          {/* 错误信息 */}
          {/* {this.state[field + "Message"] && (
            <p style={{ color: "red" }}>{this.state[field + "Message"]}</p>
          )} */}
        </div>
      );
    };
    // 判断组件是否被用户点过
    isFieldTouched = field => !!this.state[field + "Focus"];
    // 获取字段错误信息
    getFieldError = field => this.state[field + "Message"];
    render() {
      return (
        <Comp
          {...this.props}
          getFieldDec={this.getFieldDec}
          value={this.state}
          validate={this.validate}
          isFieldTouched={this.isFieldTouched}
          getFieldError={this.getFieldError}
        />
      );
    }
  };
}
  • KFormItem:只做展示
class KFormItem extends Component {
  render() {
    console.log(this.props)
    return (
      <div className="formItem">
        {this.props.children}
        {this.props.validateStatus === "error" && (
          <p style={{ color: "red" }}>{this.props.help}</p>
        )}
      </div>
    );
  }
}
  • KInput:重构 input 输入控件
class KInput extends Component{
  render(){
    return (
      <div>
        {/* 前缀图标 */}
        {this.props.prefix || ''}
        <input {...this.props}/>
      </div>
    )
  }
}
  • KFormSample:最终导出的简易表单组件
@kFormCreate
class KFormSample extends Component {
  onSubmit = () => {
    this.props.validate(isValid => {
      if (isValid) {
        alert("校验成功,提交登陆");
        console.log(this.props.value);
      } else {
        alert("校验失败");
      }
    });
  };
  render() {
    const { getFieldDec, isFieldTouched, getFieldError } = this.props;

    const userNameError = isFieldTouched("uname") && getFieldError("uname");
    const passwordError = isFieldTouched("pwd") && getFieldError("pwd");

    return (
      <div>
        <KFormItem
          validateStatus={userNameError ? "error" : ""}
          help={userNameError || ""}
        >
          {getFieldDec(
            "uname",
            {
              rules: [{ required: true, message: "请输入用户名" }]
            },
            // <input type="text" />
            <KInput type="text" prefix={<Icon type="user"></Icon>}/>
          )}
        </KFormItem>
        <KFormItem
          validateStatus={passwordError ? "error" : ""}
          help={passwordError || ""}
        >
          {getFieldDec(
            "pwd",
            {
              rules: [{ required: true, message: "请输入密码" }]
            },
            // <input type="password" />
            <KInput type="text" prefix={<Icon type="lock"></Icon>}/>
          )}
        </KFormItem>
        <button onClick={this.onSubmit}>登陆</button>
      </div>
    );
  }
}
export default KFormSample;

React未来

Fiber

  • 同步渲染 vs 异步渲染
  • React的新引擎 Fiber 的关键特性如下:
* 增量渲染(把渲染任务拆分成块,均分到帧)
* 更新是能够暂停、终止,复用渲染任务
* 给不同类型的更新赋予优先级
* 并发方面新的基础能力

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Suspense

用同步代码来实现异步操作

  • 常见的异步操作代码:
class Foo extends React.Component{
	state = {data: null};
	render(){
		if(!this.state.data){
			return null;
		} else {
			return <div>this.state.data</div>
		}
	}
	componentDidMount(){
		callAPI().then(result => {
			this.setState({data: result});
		})
	}
}
  • 组件必须要有自己的 state 和 componentDidMount 函数实现,也就不可能做成纯函数形式的组件
  • 需要两次渲染过程,第一次是 mount 引发的渲染,由 componentDidMount 触发 AJAX 然后修改 state,然后第二次渲染才真的渲染出内容
  • 代码啰嗦,十分啰嗦

使用Suspense

const Foo = () => {
	const data = createFetcher(callAJAX).read();
	return <div>{data}</div>
}
  • createFetcher还没有正式发布
    在这里插入图片描述
  • 变革:函数组件可以做数据获取,扩展了 FP 在 react 中的应用

Hooks

React v16.7.0-alpha中第一次引入了 Hooks的概念,因为这是一个alpha版本,不是正式版本,所以将来正式发布时 API 可能会有变化

useState

Hooks 的目的,是开发者可以完全抛弃class,每天早晨起床,拥抱函数式提供一个叫useState的方法,它开启了一扇新的定义 state 的门,对应的 Counter 的代码可以这么写:

import { useState } from 'react'

const Counter = () => {
	const [count, setCount] = useState(0);
	return (
		<div>
			<div>{count}</div>
			<button onClick={() => setCount(count + 1)}>+</button>
			<button onClick={() => setCount(count - 1)}>-</button>
		<div>
	);
}
  • 还可以设置多个
const [foo, updateFoo] = useState('foo');

useEffect

  • 除了useState,React还提供 useEffect ,用于支持组件中增加副作用的支持。对应的 class 写法中的生命周期:
componentDidMount(){
	document.title = `开课吧:{this.state.count}`;
}
componentDidUpdate(){
	document.title = `开课吧:{this.state.count}`;
}
import { useState, useEffect } from 'react'

const Counter = () => {
	const [count, setCount] = useState(0);

	useEffect(() => {
		document.title = `开课吧:{count}`;
	});
	return (
		<div>
			<div>{count}</div>
			<button onClick={() => setCount(count + 1)}>+</button>
			<button onClick={() => setCount(count - 1)}>-</button>
		<div>
	);
}
  • useEffect 的参数是一个函数,组件每次渲染之后,都会调用这个函数,这样就达到了 componentDidMount 和 componentDidUpdate 一样的效果。
  • Hooks会大大减少 react 的代码。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值