react 入门
Posted mengzhongfeixue
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react 入门相关的知识,希望对你有一定的参考价值。
目录
- 0、create-react-app脚手架创建项目
- 1、定义组件的方式:
- 2、jsx 编译原理:
- 3、jsx中添加类样式的方式:
- 4、何时不能使用函数式组件
- 5、受控组件的value指定为prop,组件的输入文本内容将不能直接编辑
- 6、正确地使用 State
- 7、 向事件处理程序传递参数
- 8、ref 和回调 refs
- 9、 去除某频繁改动的组件的不必要的重复渲染
- 10 周期钩子
static getDerivedStateFromProps (props){}
的用法 - 11、 函数式组件也能添加状态了(React新特性 hook ,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。)
- 12、context 全局传参 (不过一般用redux)
- 13、 HOC 高阶组件
- 14、 redux
- 15、react-router
- 16、常用富文本编辑器: KindEditor、 UEditor(百度的)、 wangeditor、 edit.md(支持markdown) 等
- 17、数据可视化:
- 18、数据深拷贝
- 19、 immutable 实现持久化数据结构
- 20、mobx 更简单的状态管理,但它是直接修改状态,非纯函数有副作用
- 21、immutable、react-immutable 和 mobx、mobx-react 完成 redux 改造(没必要,个人感觉redux保存初始数据且可维护性更好)
react diff算法 -> fiber算法
小贴士:小图标库 https://emojipedia.org/
0、create-react-app脚手架创建项目
npx create-react-app test-react
//通过npx 使用create-react-app脚手架创建项目test-react //npx 临时安装一个模块
//此时如果执行 npm run eject
,则执行解包,把构建配置写入package.json,及生成config和scripts文件夹等,且解包后不能再打包回去了。解包之前的package.json里的scripts脚本命令也自动改变。 使用中根据需要选择是否解包配置。
package.json文件dependencies中react-dom用于创建前端组件(app用react-native),react-scripts用于构建(底层封装的webpack)
1、定义组件的方式:
① 函数方式:
import React from 'react'
import ReactDom from 'react-dom'
//函数方式原理:
// 1、初探: 将xml格式虚拟DOM直接赋值给一个变量
// const app = <h1> welcome React 16.8 </h1>
// 2、深入:是否可以灵活的动态改变虚拟dom的属性或子元素内容呢?
// 2.1 定义成一个方法传参进去,然后返回该虚拟dom.
// const render = (props)=> <h1>Welcome {props.title}</h1>
// 2.2 然后,创建组件时传入参数。
// const app = render({
// title:'React 16.8'
// })
const App = (props)=>{
return (
<div> {/* jsx 虚拟dom元素不要加引号*/}
{/* 只要是jsx里要插入js代码,加一层花括号(包括注释),两种语法格式且可嵌套交替使用*/}
<h1 title={props.title}>Welcome {props.title}!!!</h1>
</div>
)
}
//将虚拟dom组件渲染到网页真正dom树上
ReactDom.render(
<App title='ksfg' />,
document.querySelector('#root') //渲染到public/index.html
// ,callback
)
② 类继承React.Component: 常用(因为其可以构建状态和管理、声明周期设置等)
import React, {Component} from 'react'
import { render} from 'react-dom'
class App extends Component {
render(){ //React.Component提供的方法
<div>
<h1>类组件</h1>
<p>{this.props.title}</p>
</div>
}
}
//将虚拟dom组件渲染到网页真正dom树上
render(
<App title='类组件继承自React.Component' />
document.querySelector('#root')
)
2、jsx 编译原理:
① 创建虚拟dom树对象
const vdom = {
tag: 'div',
attrs:{
className: ['active',''],
id:'root'
},
children:[
{
tag:'',
attrs:{},
children:[
{...}
]
}
]
}
② 调用 React.createElement(type,attrs={},[...children])
方法 把vdom树渲染到页面;
③ React.createElement()
方法内部根据 children 递归调用该方法把所有dom元素渲染出来。
3、jsx中添加类样式的方式:
① 添加内联样式
const color = { color:'#F00'}
<p style={color}>添加样式1</p>
或 <p style={{color:'#F00'}}>添加样式1</p> //JS语句写入大括号内
② 添加外部引入css文件
import './index.css'
<p className="color-red">添加样式1</p>
③ 动态(是否)添加样式 (npm i classnames -S
)
import classnames from 'classnames'
<p className={classnames('a',{'b':true,'c':false})}>添加样式1</p>
{/*添加了'a'和'b'样式*/}
④ styled-components插件标签方式添加样式 (npm i styled-components -S
)
import styled from 'styled-components'
const Title = style.h1`
color:#F00
`
<Title>添加样式1</Title>
⑤ styled-jsx 插件 jsx方式添加样式
4、何时不能使用函数式组件
① 有状态 state 时;
② 要使用生命周期钩子时;
③ 需要使用 ref 时(因为ref要挂到实例上,函数式组件不能创建实例,但你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件);
.....
5、受控组件的value指定为prop,组件的输入文本内容将不能直接编辑
在受控组件上指定 value 的 prop 可以防止用户更改输入。如果指定了 value,但输入仍可编辑,则可能是意外地将value 设置为 undefined 或 null。
6、正确地使用 State
① 不要直接修改 State ,不会重新渲染组件 ,而是应该使用 setState() 。构造函数是唯一可以给 this.state 赋值的地方。
② State 的更新可能是异步的,出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用 。 因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态 ,要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数 。
③ State 的更新会被合并 , 当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。
7、 向事件处理程序传递参数
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。
8、ref 和回调 refs
① Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
② 回调 Refs :
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
9、 去除某频繁改动的组件的不必要的重复渲染
① 把该组件改为 PureComponent 先进行前比较(一般就不会再重复多次渲染了)
② 使用 shouldComponentUpdate 进行再比较
shouldComponentUpdate ( nextProps,nextState){
return (nextProps.xxx !== this.props.xxx) || (nextState.yyy !== this.state.yyy)
}
比如:下面这样在TodoList中某个 ListItem 改变就不会渲染所有的 ListItem 了。
import React, { PureComponent } from 'react'
const noop = ()=>{}
export default class ListItem extends pureComponent {
handleCheckboxChange = ()=>{
/* this.props.completedChange && this.props.completedChange(this.props.id) */
const { completedChange=noop, id } = this.props
/* completedChange && completedChange(id) */
completedChange(id)
}
handleDeleteItem = ()=>{
this.props.deleteItem(this.props.id)
}
render() {
return (
<li>
<input type="checkbox" checked={this.props.isCompleted} onChange={ this.handleCheckboxChange} />
<span> {this.props.title} {this.props.isCompleted?'已完成??':'未完成??'} </span>
<button onClick={ this.handleDeleteItem} >删除 </button>
</li>
)
}
}
10 周期钩子static getDerivedStateFromProps (props){}
的用法
示例:
static getDerivedStateFromProps (props){
return {
completedText: props.isCompleted ? '完成' : '未完成'
}
}
11、 函数式组件也能添加状态了(React新特性 hook ,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。)
import React, { useState, useEffect} from 'react' // useState和 useEffect 是 hook 常见的两个API
const Counter = ()=> {
const [count,setCount] = useState(0) //useState是一个方法,该方法的参数就是默认值,结果是一个数组,数组的第一个参数就是state,第二个就相当于setState
const [title,setTitle] = useState('数字递增或递减') //一个函数式组件可以有多个state
useEffect(()=>{
// useEffect 的参数是一个回调函数,不管组件挂载还是更新,都会触发这个回调,类似于 componentDidMount 和 componentDidUpdate 的结合
console.log('渲染了')
document.title = '当前的数是${count}'
})
return (
<div>
<p>{title}</p>
{ /* 这里的setCount就是useState所生成的方法,注意和setState不同的地方在于参数,这里的参数就是一个新值即可 */}
<button onClick={()=>{ setCount(count-1)}}> - </button>
<span>{count}</span>
<button onClick={()=>{ setCount(count+1)}}> + </button>
</div>
)
}
12、context 全局传参 (不过一般用redux)
示例如下:
// createContext 是react 提供的用于跨多级组件传值的方法
import React , {Component, createContext} from 'react'
import {render} from 'react-dom'
// createContext 这个方法的结果是一个对象,里面有两个组件:Provider 和 Consumer , 其中 Provider 用于提供状态, Consumer 用于接收状态
const {
Provider,
Consumer: CounterConsumer
// 解构出来的 Consumer 重新赋值为 CounterConsumer 组件
} = createContext()
// 封装 Provider ,因为直接使用 Provider 不方便管理状态
class CounterProvider extends Component {
constructor(){
super()
// 这里的状态是共享的,任何 CounterProvider 的后代组件都可以通过 CounterConsumer 来接收 Provider提供的 value 值。
this.state = {
count: 100
}
}
//下面的两个方法也可以 传递下去
incrementCount = () =>{
this.setState({
count: this.state.count + 1
})
}
decrementCount = () =>{
this.setState({
count: this.state.count - 1
})
}
render (){
return (
// 使用Provider 组件,必须要有一个 value 值,用于向后代组件传值,一般是一个对象,因为对象比较灵活。
<Provider value={{
this.state.count,
onIncrementCount: this.incrementCount
onDecrementCount: this.decrementCount
}}>
{ this.props.chidren}
</Provider>
)
}
}
class Counter extends Component {
render (){
return (
// Provider 的后代组件通过 Consumer 组件来接收 Provider 提供的 value 值
<CounterConsumer>
{
// 注意! Consumer 的 children 必须是一个方法,且该方法接收的参数即是 Provider 的 value 值
({count})=>{ // 解构出 count
return <span>{count}</span>
}
}
</CounterConsumer>
)
}
}
class CountBtn extends Component {
render (){
return (
{
({onIncrementCount,onDecrementCount}) => {
const handlerClick = this.props.type === 'increment' ? onIncrementCount : onDecrementCount
return <Button onClick ={ handlerClick}> {this.props.children} </Button>
}
}
<Button > {this.props.chidren} </Button>
)
}
}
class App extends Component {
render (){
return (
<>
<CountBtn type="decrement" />
<Counter />
<CountBtn type="increment" />
</>
)
}
}
render(
// Provider 组件 向后代传值的形式
<CounterProvider>
<App />
</CounterProvider>,
document.getElementById('root')
)
13、 HOC 高阶组件
import React ,{ Component} from 'react'
const withCopyright = (YourComponent) =>{
return class WithCopyright extends Component {
render (){
return (
<>
// 高阶组件拦截了 YourComponent 传入参数,再把参数还给 YourComponent(自己理解的 )
<YourComponent {...this.props} />
<div> © 2019   千锋教育 </div>
</>
)
}
}
}
@ withCopyright
class YourComponent extends Component {
render(){
return (
<div name="传递给高阶组件的值">
要添加高阶组件的组件
</div>
)
}
}
让 create-react-app 支持@ 装饰器写法 ,需要做的配置:
1、不管你是要配置什么,我们最好的方式是使用 react-app-rewired 这个包来对cra 创建 的项目进行略微 的配置调整, `npm install customize-cra --save-dev`
2、 安装好之后,在package.json 里把 scripts 里的 react-scripts 替换成 react-app-rewired
3、 在根目录下创建一个 config-overrides.js
module.exports = (config) =>{
// 如果没使用customize-cra,在这里配置
return config
}
4、 当然如果想要配置更方便,可以再安装 customize-cra,`npm install customize-cra --save-dev`,然后把 config-overrides.js 改成这样
const {override,addDecoratorsLegacy} = require('customize-cra')
module.exports = override(
addDecoratorsLegacy()
)
5、 `npm i @babel/plugin-proposal-decorators -D`
14、 redux
① Flux 架构思想
* view 视图层
* ActionCreator(动作创造者) : 视图层发出的消息(比如 mouseClick)
* Dispatcher(派发器): 用来接收 Actions 、 执行回调函数
* Store(数据层): 用来存放应用的状态,一旦发生变动,就提醒 Views 要更新页面
② Flux 的流程:
1. 组件获取到store中保存的数据挂载在自己的状态上
2、用户产生了操作,调用actions的方法
3、actions接收到用户的操作,进行一系列的逻辑判断、异步操作
4、然后actions会创建出对应的action,action带有标识性的属性
5、actions调用dispatcher的dispatch方法,将action传递给dispatcher
6、dispatcher接收到action并根据标识信息判断之后调用store的更改数据的方法
7、store的方法被调用后,更改状态,并触发自己的某一个事件
8、store更改状态后事件被触发,该事件的处理程序会通知view去获取最新的数据
③ Redux: Flux架构思想的一种实现
Redux 使用的三大原则:
1、single Source of Truth(唯一的数据源)
2、State is read-only (状态是只读的)
3、Changes are made with pure function(数据的改变必须通过纯函数完成)
④ redux 原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title> Redux 原理 </title>
</head>
<body>
<div>
<button onClick="dispatch({type:"DECREASEMENT",n:2})">-</button>
<span id="countDisplay">10</span>
<button onClick="dispatch({type:"INCREASEMENT",n:1})">+</button>
</div>
<script>
const countDisplay = document.querySelector('#countDisplay')
const countState = {
count: 5
}
const renderCount = (state) =>{
countDisplay.innerHTML = state.count
}
renderCount(countState)
const reducer = (state,action)=>{
if(!state){
return countState
}
switch(action.type){
case 'DECREASEMENT':
return {
...state,
count: state.count - action.n
}
case 'INCREASEMENT':
return {
...state,
count: state.count + action.n
}
default:
break
}
}
const createStore = (reducer)=>{
let state = null
const getState = ()=> state
const listeners = []
const subscribe = (listener)=>listeners.push(listener)
const dispatch = (action)=>{
reducer(state,action)
listeners.forEach(listener =>listener())
}
return {
getState,
subscribe,
dispatch
}
}
const store = createStore(reducer)
store.subscribe(renderCount)
</script>
</body>
</html>
- redux 和react-redux 使用流程: 创建reduers -> 合并reducers -> 创建
store=createStore(reducer)
-> 顶层组件通过Provider使用store,<Provider store={store}>
-> 后代组件通过 connect 接收store,connect(mapStateToProps,{...actionCreators})(YourComponent)
-> 创建 action -> 根据action.type 配置actionCreators - react-redux 处理流程: actionCreator -> 自动 dispatch(actionCreator()) -> reducer -> store -> view -> 自动 dispatch(actionCreator())...
- 处理异步action 流程: actionCreator -> middleware处理生成新的action -> 修改dispatch(action) -> reducer -> store -> view -> dispatch(actionCreator())
15、react-router
import React from 'react'
import { render } from 'react-dom'
import { BrowerRouter as Router, Route } from 'react-router-dom'
import App from './App'
render(
{/* Router 组件只用在顶层,Route组件用在各个要跳转的后代组件上*/}
<Router>
<Route component={App} path='/' />
</Router>,
document.querySelector('#root')
)
App.js:
import React, {Component} from 'react'
import { Home,Article,User,NotFound} from './views'
import {ArticleDetail} from './Article/ArticleDetail'
// NavLink 组件比Link组件多了一个active的class,Redirect组件要搭配Switch使用
import {Route, NavLink as Link, Redirect, Switch } from 'react-router-dom'
export default class App extends Component {
render(){
return (
<div>
<ul>
<li> <Link to='/home'>首页</Link> </li>
<li> <Link to='article'>文章</Link> </li>
<li> <Link to='/user'>用户</Link></li>
</ul>
{/* Switch 组件包含的路由匹配成功一个,便返回不再往下匹配*/}
<Switch>
{/* Route包含的组件默认都有history、props等属性 */}
<Route component={Home} path='/home' />
{/* 加入exact属性,表示不匹配子路由,默认是匹配该路径和其子路径的 */}
<Route component={Article} path='/article' exact />
{/* 匹配成功的话,这里的文章详情将覆盖Article */}
<Route component={ArticleDetail} path="/article/:id" />
{/* 用render渲染组件传递参数 */}
<Route render={(routeProps)=>{
return this.state.isLogin ? <User {...routeProps} /> : <div>请登录</div>}} path='/user' />
<Redirect to="/home" from="/" exact />
<Route Component={NotFound} path="/404" />
<Redirect to="/404" />
</Switch>
</div>
)
}
}
views/articles/index.js:
import React,{Component} from 'react'
import { Link, Route } from 'react-dom'
//import ArticleDetail from './Article/ArticleDetail'
import {* as Home} from './backhome'
export default class Article extends Component {
render(){
return (
<div>
{/* query传参 */}
<Link to="/article/1?from=article"> 文章1 </Link>
{/* 隐式传参,比如可以做埋点 */}
<Link to={{pathname:'artical/2',state:{ from:'article'}}}> 文章2 </Link>
{/* <Route component={ArticleDetail} path="/article/:id" /> */}
<Home />
</div>
)
}
}
goHome = ()=> {
this.props.history.push({
pathname:'/home',
state:{
id: this.props.match.params.id
}
})
}
backhome.js :
import React,{Component} from 'react'
import {withRouter} from 'react-router-dom'
class BackHome extends Component {
handleClick = () => {
this.props.history.push({
pathname: '/home',
state:{
id: this.props.match.params.id
}
})
}
render(){
return (
<button onClick={this.handleClick}>返回首页</button>
)
}
}
export default withRouter(BackHome)
16、常用富文本编辑器: KindEditor、 UEditor(百度的)、 wangeditor、 edit.md(支持markdown) 等
- 富文本编辑器原理:
<button onclick="boldFont()">加粗<button>
<button onclick="italicFont()">倾斜<button>
<button onclick="redFont()">字体红色<button>
<div contenteditable style="border:1px solid #dedede; width:400px; min-height:300px;">
<img src="http://img5.imgtn.bding.com/it/u=394753335xxxxxxx&gp=0.jpg">
</div>
<!-- 老浏览器用<iframe designmode="on" /> -->
<script>
var boldFont = function(){
document.execCommand('bold')
}
var italicFont = function(){
document.execCommand('italic')
}
var redFont = function(){
document.execCommand('foreColor',null,'#f00')
}
</script>
17、数据可视化:
1、canvas 位图 2、 svg 矢量图 3、 三维 webgl
√4、echart 5、highcharts 6、d3 dataV antV
egret 游戏 rapheal.js等
18、数据深拷贝
import {cloneDeep} from 'lodash'
const state = {
name:'可通过assign方法浅拷贝的基本数据类型的数据,拷贝后的数据改变不影响源数据中该项的数据',
items:['通过浅拷贝后实际是拷贝过去的该复杂数据的地址','拷贝后的数据中该复杂数据项改变的话,源数据跟着变化']
}
// const newState = JSON.parse(JSON.stringify(state)) //该深拷贝方式不能拷贝方法
const newState = cloneDeep(state)
console.log(newState === state)
19、 immutable 实现持久化数据结构
- immutable 插件中常用的数据类型有 Map 、List、Set等
import {Map} from 'immutable'
const state={
name:'map',
value:['对象','方法','数组']
}
const imState = Map(state)
//比较两个数据结构的不同
console.log(state,imState)
//改变后必须要接收新的map对象
const newImState = imState.set('name','复杂数据作为键')
//map数据只能通过get获取
console.log(imState.get('name'),newImState.get('name'))
const map1 = Map({a:1,b:2});
const map2 = Map({a:1,b:2});
const map3 = map1.set('b',2);
const mapCopy = map1;
map1.equals(map2); // true
is(map1,map2); // true
map1 === map2; // false
map1 === map3; // true
import {List} from 'immutable'
const list1 = List([1,2]);
const list2 = list1.push(3,3,4);
console.log(list1.get(4),list2.get(4)) // undefined 3
import {fromJS,toJS} from 'immutable'
const state = {
name: 'qf',
courses:['h5','java','python'],
obj:{
x:1,
y:{
z:1
}
}
}
//复杂的JS数据类型转换为immutable,很常用
const imState = fromJS(state)
console.log(imState.get('courses').get(0))
console.log(imState.getIn(['courses',0]))
console.log(imState.getIn(['obj','x','y']))
const newImState = imState.setIn(['obj','y','z'],100)
const newImState = imState.updateIn(['obj','y','z'],v=>v+1)
const jsState = newImState.toJS().obj.y.z
使用 immutable、react-immutable改造redux中reducer的state数据类型:
1、 `npm i immutable redux-immutable -S` 2、reducers文件夹counter.js中修改 import {combineReducers} from 'redux' ---> import {combineReducers} from 'redux-immutable' 3、reducers文件夹counter.js中修改 const initState = { count:10 } ---> import { fromJS} from 'immutable' const initState = fromJS({ count:10 }) 4、reducers文件夹counter.js中修改 case 'INCREAMENT': return { ...state, count:state.count+1 } ---> case 'INCREAMENT': return state.updateIn(['count'],v=>v+1) case 'DECREAMENT': return { ...state, count:state.count-1 } ---> case 'DECREAMENT': return state.update('count',v=>v-1) 5、在调用conncect的组件中修改 const mapStateToProps = state =>{ return { count:state.counter.count ---> count: state.getIn(['counter','count']) } }
20、mobx 更简单的状态管理,但它是直接修改状态,非纯函数有副作用
? 1、npm i mobx mobx-react -S
? 2、store.js:
import {observable,computed,action} from 'mobx'
class Counter {
name = 'Counter App'
@observable count = 100
@computed get doubleCount(){
return this.count*2
}
@action.bound increment(){
this.count += 1
}
}
const counterState = new Counter()
export default counterStore
index.js:
import React from 'react'
import ReactDom from 'react-dom'
import {Provider} from 'mobx-react'
import App from '/App'
import couterStore from './store'
ReactDom.render(
<Provider counter={counterStore}>
<App />
</Provider>,
document.getElementById('root')
)
App.js:
import React,{Component} from 'react'
import {inject,observer} from 'mobx-react'
@inject('counter')
@observer
export default class App extends Component{
render(){
return (
<div>
<CounterDisplay counter={this.props.counter} />
<button onClick={this.props.counter.increment}>+</button>
</div>
)
}
}
@inject((store)=>{
return {
count:store.counter.count,
doubleCount:store.counter.doubleCount
}
})
@observer
class CounterDisplay extends Component{
render(){
return (
<span>原值:{this.props.count}</span>
<span>二倍值:{this.props.doubleCount}</span>
)
}
}
21、immutable、react-immutable 和 mobx、mobx-react 完成 redux 改造(没必要,个人感觉redux保存初始数据且可维护性更好)
以上是关于react 入门的主要内容,如果未能解决你的问题,请参考以下文章
[React Testing] Use Generated Data in Tests with tests-data-bot to Improve Test Maintainability(代码片段