import { Layout, Menu } from "antd";
import Link from "umi/link";
import styles from "./index.css";
import React, { Component } from "react";
import { Icon, Badge, Dropdown } from "antd";
import { connect } from "dva";
const { Header, Footer, Content } = Layout;
export default class extends Component {
onItemClick = item => {
console.log(item);
};
render() {
const selectedKeys = [this.props.location.pathname];
return (
// 上中下布局
<Layout>
{/* 页头 */}
<Header className={styles.header}>
<img className={styles.logo} src="https://img.kaikeba.com/logo-new.png"/>
<Menu theme="dark" mode="horizontal" selectedKeys={selectedKeys}
style={{ lineHeight: "64px", float: "left" }}>
<Menu.Item key="/">
<Link to="/">商品</Link>
</Menu.Item>
<Menu.Item key="/users">
<Link to="/users">用户</Link>
</Menu.Item>
<Menu.Item key="/about">
<Link to="/about">关于</Link>
</Menu.Item>
</Menu>
{/* 购物车信息 */}
...
</Header>
{/* 内容 */}
<Content className={styles.content}>
<div className={styles.box}>{this.props.children}</div>
</Content>
{/* 页脚 */}
<Footer className={styles.footer}>开课吧</Footer>
</Layout>
);
}
}
import {Exception} from 'ant-design-pro'
export default function() {
return (
<Exception type="404" backText="返回首页"></Exception>
);
}
import React, { Component } from "react";
import styles from "./login.css";
import { Login } from "ant-design-pro";
import { connect } from "dva";
const { UserName, Password, Submit } = Login;
@connect()
export default class extends Component {
onSubmit = (err, values) => {
console.log("用户输入:", values);
if (!err) {
// 校验通过,提交登录
this.props.dispatch({ type: "user/login", payload: values });
}
};
render() {
return (
<div className={styles.loginForm}>
{/* logo */}
<img className={styles.logo} src="https://img.kaikeba.com/logo-new.png"/>
{/* 登录表单 */}
<Login onSubmit={this.onSubmit}>
<UserName name="username" placeholder="kaikeba" rules={[{ required: true, message: "请输入用户名" }]}/>
<Password name="password" placeholder="123" rules={[{ required: true, message: "请输入密码" }]}/>
<Submit>登录</Submit>
</Login>
</div>
);
}
}
export default {
"post /api/login"(req, res, next) {
const { username, password } = req.body;
console.log(username, password);
if (username == "kaikeba" && password == "123") {
return res.json({
code: 0,
data: {
token: "kaikebaisgood",
role: "admin",
balance: 1000,
username: "kaikeba"
}
});
}
if (username == "jerry" && password == "123") {
return res.json({
code: 0,
data: {
token: "kaikebaisgood",
role: "user",
balance: 100,
username: "jerry"
}
});
}
return res.status(401).json({
code: -1,
msg: "密码错误"
});
}
};
import axios from "axios";
import router from "umi/router";
const userinfo = JSON.parse(localStorage.getItem("userinfo")) || {
token: "",
role: "",
username: "",
balance: 0
};
// api
function login(payload) {
return axios.post("/api/login", payload);
}
export default {
namespace: "user", // 可省略
state: userinfo, // 初始状态:缓存或空对象
effects: {
// action: user/login
*login({ payload }, { call, put }) {
try {
const {
data: { code, data: userinfo }
} = yield call(login, payload);
if (code == 0) {
// 登录成功: 缓存用户信息
localStorage.setItem("userinfo", JSON.stringify(userinfo));
yield put({ type: "init", payload: userinfo });
router.push("/");
} else {
// 登录失败:弹出提示信息,可以通过响应拦截器实现
}
} catch (error) {}
}
// *logout({ payload }, { call, put }) {
// localStorage.removeItem("userinfo");
// yield put({ type: "clear" });
// router.push("/login");
// }
},
reducers: {
init(state, action) {
// 覆盖旧状态
return action.payload;
}
// clear(state, action) {
// return initUserinfo;
// }
}
};
return res.status(401).json({
code: -1,
msg: "密码错误"
});
响应拦截,创建 src/interceptor.js
import axios from "axios";
import { notification } from "antd";
const codeMessage = {
202: "一个请求已经进入后台排队(异步任务)。",
401: "用户没有权限(令牌、用户名、密码错误)。",
404: "发出的请求针对的是不存在的记录,服务器没有进行操作。",
500: "服务器发生错误,请检查服务器。"
};
// 仅拦截异常状态响应
axios.interceptors.response.use(null, ({ response }) => {
if (codeMessage[response.status]) {
notification.error({
message: `请求错误 ${response.status}: ${response.config.url}`,
description: codeMessage[response.status]
});
}
return Promise.reject(err);
});
拦截器生效,创建 src/global.js:
import interceptor from './interceptor'
// 课程列表
const course={
data:{
'Javascript':[
{id:1,name:'ES6语法实战',img:'LearnES6_Final.png',price:'100',solded:'561',},
{id:2,name:'Typescript实战',img:'Typescript_Plumbing_image.png',price:'100',solded:'156',},
{id:3,name:'Javascript算法实战',img:'JSBasic-Algorithms_Final.png',price:'100',solded:'526',},
],
'React':[
{id:4,name:'React入门',img:'ReactBeginners.png',price:'100',solded:'536',},
{id:5,name:'ReactNative开发自己的APP',img:'ReactNative.png',price:'100',solded:'456',},
{id:6,name:'React服务端渲染实战',img:'ReactNextServer_Final.png',price:'100',solded:'556',},
{id:7,name:'Redux Sage中间件实战',img:'ReduxSaga.png',price:'100',solded:'2256',},
{id:8,name:'试用react开发PWA应用',img:'PWAReact_Final.png',price:'100',solded:'1156',},
{id:9,name:'React Hooks实战',img:'SimplifyHooks_Final.png',price:'100',solded:'5361',},
{id:10,name:'React Mobx状态管理实战',img:'React_Mobx_TS.png',price:'100',solded:'956',},
],
'Vuejs':[
{id:11,name:'Vue进阶实战',img:'VueJS_Final.png',price:'180',solded:'586',},
{id:12,name:'Vuejs开发pwa应用',img:'VuePwa.png',price:'100',solded:'596',},
{id:13,name:'试用TS开发Vuejs应用',img:'TSVue_Final.png',price:'100',solded:'526',},
],
'Git':[
{id:14,name:'Github从入门到精通',img:'github.png',price:'99',solded:'10',},
{id:15,name:'Git版本控制实战',img:'LearnGit.png',price:'49',solded:'180',},],
'Test':[
{id:16,name:'Puppetee测试入门',img:'TestGooglePuppeteer_Final.png',price:'10',solded:'56',},
{id:17,name:'使用jest测试你的React项目',img:'TestReactJest.png',price:'30',solded:'10',},
],
'Python':[
{id:18,name:'Python从入门到精通',img:'IntroPython.png',price:'100',solded:'56',},
],
'Node.js':[
{id:19,name:'使用Docker部署你的nodejs',img:'NodeDocker_1000.png',price:'100',solded:'56',},
{id:20,name:'在AWS环境部署nodejs',img:'NodeAWSServerless_Final.png',price:'100',solded:'56',},
],
'GraphQL':[
{id:21,name:'GraphQL从入门到精通',img:'GraphQL_Final.png',price:'100',solded:'56',},
]
},
tags:['Javascript','React','Vuejs','Git','Test','Python','Node.js','GraphQL']
}
// 课程分类
course.tags.forEach(tag=>{
course.data[tag].forEach(v=>{
v.tag = tag
})
})
export default {
// "method url": Object 或 Array
// "get /api/goods": {
// result: data
// },
// "method url": (req, res) => {}
"get /api/goods": function(req, res, next) {
setTimeout(() => {
res.json({
code: 0,
data: course
});
}, 2500);
}
};
import axios from "axios";
// api
function getGoods() {
return axios.get("/api/goods");
}
export default {
namespace: "goods",
state: {
courses: {}, // 课程
tags: [] // 分类
},
effects: {
*getList(action, { call, put }) {
const { data: {data: courseData} } = yield call(getGoods);
yield put({ type: "initGoods", payload: courseData });
}
},
reducers: {
initGoods(state, { payload }) {
const { tags, data: courses } = payload;
return { ...state, tags, courses };
},
addGood(state, action) {
console.log(action);
return [...state, { title: action.payload.title }];
}
}
};
import React, { Component } from "react";
import { Card, Row, Col, Icon, Skeleton } from "antd";
import { TagSelect } from "ant-design-pro";
import { connect } from "dva";
@connect(
state => ({
courses: state.goods.courses,
tags: state.goods.tags,
// loading: state.loading
}),
{
addCart: item => ({type: "cart/addCart",payload: item}),
getList: () => ({type: "goods/getList"})
}
)
class Goods extends Component {
constructor(props) {
super(props);
// displayCourses为需要显示的商品数组
this.state = {
tags: [], // 默认未选中
displayCourses: new Array(8).fill({}) // 设置size可用于骨架屏展示
};
}
componentDidMount() {
this.props.getList();
}
componentWillReceiveProps(props) {
// 数据传入时执行一次tagSelectChange
if (props.tags.length) {
this.tagSelectChange(props.tags, props.courses);
}
}
tagSelectChange = (tags, courses = this.props.courses) => {
// 过滤出显示数据
let displayCourses = [];
tags.forEach(tag => {
displayCourses = [...displayCourses, ...courses[tag]];
});
this.setState({ displayCourses, tags });
};
addCart = (e, item) => {
e.stopPropagation();
this.props.addCart(item);
};
render() {
// if (this.props.loading.models.goods) {
// return 加载中...;
// }
return (
<div>
{/* 分类标签 */}
...
{/* 商品列表 */}
...
</div>
);
}
}
export default Goods;
<TagSelect onChange={this.tagSelectChange} value={this.state.tags}>
{this.props.tags.map(tag => {
return (
<TagSelect.Option key={tag} value={tag}>{tag}</TagSelect.Option>
);
})}
</TagSelect>
<Row type="flex" justify="start">
{this.state.displayCourses.map((item, index) => {
return (
// span=6表示4列
<Col key={index} style={{ padding: 8 }} span={6}>
{item.name
? (<Card extra={<Icon onClick={e => this.addCart(e, item)} type="shopping-cart" style={{ fontSize: 18 }}/>}
onClick={() => this.toDetail(item)} hoverable title={item.name} cover={<img src={"/course/" + item.img} />} >
<Card.Meta description={
<div>
<span>¥{item.price}</span>
<span style={{ float: "right" }}>
<Icon type="user" /> {item.solded}
</span>
</div>
} />
<div />
</Card>)
: (<Skeleton active={true} />)}
</Col>
);
})}
</Row>
export default {
namespace: "cart", // 可省略
state: JSON.parse(localStorage.getItem("cart")) || [], // 初始状态:缓存或空数组
reducers: {
addCart(cart, action) {
const good = action.payload;
const idx = cart.findIndex(v => v.id == good.id);
if (idx > -1) {
// 更新数量
const cartCopy = [...cart];
const itemCopy = { ...cartCopy[idx] };
itemCopy.count += 1;
cartCopy.splice(idx, 1, itemCopy);
return cartCopy;
} else {
// 新增
return [...cart, { ...good, count: 1 }];
}
}
}
};
export const dva = {
config: {
onStateChange(state,a) {
if (localStorage) {
localStorage.setItem("cart", JSON.stringify(state.cart));
}
}
}
};
...
export default class extends Component {
...
render() {
....
return (
// 上中下布局
<Layout>
{/* 页头 */}
<Header className={styles.header}>
...
{/* 购物车信息 */}
<Dropdown overlay={menu} placement="bottomRight">
<div className={styles.cart}>
<Icon type="shopping-cart" style={{ fontSize: 18 }} />
<span>我的购物车</span>
<Badge count={this.props.count} offset={[-4, -18]} />
</div>
</Dropdown>
</Header>
{/* 内容 */}
...
{/* 页脚 */}
...
</Layout>
);
}
}
@connect(state => ({
count: state.cart.length,
cart: state.cart
}))
export default class extends Component {
const menu = (
<Menu>
{this.props.cart.map((item, index) => (
<Menu.Item key={index}>
{item.name}×{item.count} <span>¥{item.count * item.price}</span>
</Menu.Item>
))}
</Menu>
);
render(){
return (
...
{/* 购物车信息 */}
<Dropdown overlay={menu} placement="bottomRight">
...
</Dropdown>
...
);
}
}
React源码:https://github.com/facebook/react/blob/master/packages/react/src/React.js
const React = {
Children: {
map,
forEach,
count,
toArray,
only,
},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
lazy,
memo,
error,
warn,
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
Fragment: REACT_FRAGMENT_TYPE,
Profiler: REACT_PROFILER_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
version: ReactVersion,
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
// Note: some APIs are added with feature flags.
// Make sure that stable builds for open source
// don't modify the React object to avoid deopts.
// Also let's not expose their names in stable builds.
if (enableStableConcurrentModeAPIs) {
React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
React.unstable_ConcurrentMode = undefined;
}
export default React;
let React = {
createElement,
Component,
PureComponent
}
为什么需要js?
JSX是对 js 语法的扩展,使我们可以用类似 xml 方式描述视图。
为什么需要JSX?
执行快、类型安全、简单快速-提高开发效率
import React from 'react'
import ReactDOM from 'react-dom'
function Comp(props){
return <h2>hi, {props.name}</h2>
}
const jsx = (
<div id="demo">
<span>hi</span>
<Comp name="kaikeba"></Comp>
</div>
)
console.log(JSON.stringify(jsx, null, 2))
ReactDOM.render(jsx, document.querySelector('#root'))
{
"type": "div",
"key": null,
"ref": null,
"props": {
"id": "demo",
"children": [
{
"type": "span",
"key": null,
"ref": null,
"props": {
"children": "hi"
},
"_owner": null,
"_store": {}
},
{
"key": null,
"ref": null,
"props": {
"name": "kaikeba"
},
"_owner": null,
"_store": {}
}
]
},
"_owner": null,
"_store": {}
}
// import React from 'react'
import React, {Component} from './kreact'
// import ReactDOM from 'react-dom'
import ReactDOM from './kreact-dom'
// 函数组件
function CompFunc(props){
return <h2>function组件 hi, {props.name}</h2>
}
// class组件
class CompClass extends Component {
render() {
return <h2>class组件</h2>
}
}
const users = [
{id: 1, name: 'jerry'},
{id: 2, name: 'tom'}
];
const jsx = (
<div id="demo" style="color: red" className="box">
<span>hi</span>
<CompFunc name="kaikeba"></CompFunc>
<CompClass></CompClass>
<ul>
{users.map(u => (<li key={u.id}>{u.name}</li>))}
</ul>
</div>
)
console.log(jsx)
// ReactDOM.render(React 原理
, document.querySelector('#root'))
ReactDOM.render(jsx, document.querySelector('#root'))
import {createVNode} from './kvdom'
function createElement(type, props, ...children){
// console.log(arguments)
// 传递的类型有三种:1-原生标签,2-函数式组件,3-class组件
// 使用 vtype 属性表示元素类型
props.children = children
console.log({type, props})
delete props.__source
delete props.__self
// 判断组件类型
let vtype;
if(typeof type === 'string'){
// 原生标签,div、span、...
vtype = 1;
}else if(typeof type === 'function'){
if(type.isClassComponent){
// 类组件
vtype = 2;
} else{
// 函数组件
vtype = 3;
}
}
return createVNode(vtype, type, props)
}
// 实现Component
export class Component {
// 区分function和class组件
static isClassComponent = true;
constructor(props){
this.props = props;
this.state = {};
}
setState(state){
// ...
}
}
export default {
createElement
}
import { initVNode } from "./kvdom";
function render(vnode, container) {
// container.innerHTML = `${JSON.stringify(vnode, null, 2)}
`
container.appendChild(initVNode(vnode))
}
export default {
render
}
// 转换 vdom 为 dom
export function initVNode(vnode){
// vnode是一颗虚拟DOM树
const {vtype} = vnode
if(!vtype){
// 文本节点,TextNode
return document.createTextNode(vnode);
}
if(vtype === 1){
// 原生节点
return createElement(vnode);
}else if(vtype === 2) {
// class 组件
return createClassComp(vnode);
}else{
return createFunctionComp(vnode);
}
}
function createElement(vnode){
const {type, props} = vnode;
const node = document.createElement(type);
// 属性处理
const {key, children, ...rest} = props;
Object.keys(rest).forEach(attr => {
// 特殊处理的属性:htmlFor,className
if(attr === 'className'){
node.setAttribute('class', rest[attr])
}else{
node.setAttribute(attr, rest[attr])
}
});
// 递归处理可能存在的子元素
children.forEach(c => {
// c 如果是数组
if(Array.isArray(c)){
c.forEach(n => {
node.appendChild(initVNode(n))
});
}else{
node.appendChild(initVNode(c))
}
});
return node;
}
function createClassComp(vnode){
const {type, props} = vnode;
console.log(type)
const component = new type(props);
// 执行 class 组件的render方法得到vdom
const vdom = component.render();
return initVNode(vdom)
}
function createFunctionComp(vnode){
const {type, props} = vnode;
// type 是函数组件,它执行直接返回vdom
const vdom = type(props);
return initVNode(vdom)
}
// vdom diff
// ...
export function createVNode(vtype, type, props){
// 传递的类型有三种:1-原生标签,2-函数式组件,3-class组件
// 使用 vtype 属性表示元素类型
const vnode = {
vtype, type, props
}
return vnode
}
import shallowEqual from './shallowEqual'
import Component from './Component'
export default function PureComponent(props, context){
Component.call(this, props, context)
}
PureComponent.prototype = Object.create(Component.prototype)
PureComponent.prototype.constructor = PureComponent
PureComponent.prototype.isPureReactComponent = true
PureComponent.prototype.shouldComponentUpdate = shallowCompare
function shallowCompare(nextProps, nextState){
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}
class App extends React.Component {
constructor(props){
super(props)
this.state = {num: 1}
}
componentDidMount(){
setInterval(() => {
this.setState({num: this.state.num + 1})
}, 1000)
}
render(){
return <div>
<h2>你好,{this.props.name}</h2>{this.state.num}
<div>
}
}
export let updateQueue = {
updaters: [],
isPending: false,
add(updater) {
_.addItem(this.updaters, updater)
},
batchUpdate(){
if(this.isPending){
return
}
this.isPending = true
let { updaters } = this
let updater
while (updater = updaters.pop()) {
updater.updateComponent()
}
this.isPending = false
}
}
function Updater(instance){
this.instance = instance
this.pendingStates = []
this.pendingCallbacks = []
this.isPending = false
this.nextProps = this.nextContext = null
this.clearCallbacks = this.clearCallbacks.bind(this)
}
Updater.prototype = {
emitUpdate(nextProps, nextContext) {
this.nextProps = nextProps
this.nextContext = nextContext
nextProps || !updateQueue.isPending
? this.updateComponent()
: updateQueue.add(this)
},
updateComponent() {
let {instance, pendingStates, nextProps, nextContext} = this
if(nextProps || pendingStates.length>0){
nextProps = nextProps || instance.props
nextContext = nextContext || instance.context
this.nextProps = this.nextContext = null
shouldUpdate(instance, nextProps, this.getState(), nextContext, this.clearCallbacks)
}
},
addState(nextState){
if(nextState){
_.addItem(this.pendingStates, nextState)
if(!this.isPending){
this.emitUpdate()
}
}
},
replaceState(nextState){
let {pendingStates} = this
pendingStates.pop()
_.addItem(pendingStates, [nextState])
},
getState(){
let {instance, pendingStates} = this
let {state, props} = instance
if(pendingStates.length){
state = _.extend({}, state)
pendingStates.forEach(nextState => {
let isReplace = _.isArr(nextState)
if(isReplace){
nextState = nextState[0]
}
if(_.isFn(nextState)){
nextState = nextState.call(instance, state, props)
}
if(isReplace){
state = _.extend({}, nextState)
}else{
_.extend(state, nextState)
}
})
pendingStates.length = 0
}
return state
},
clearCallbacks(){
let {pendingCallbacks, instance} = this
if(pendingCallbacks.length >0){
this.pendingCallbacks = []
pendingCallbacks.forEach(callback => callback.call(instance))
}
},
addCallback(callback){
if(_.isFn(callback)){
_.addItem(this.pendingCallbacks, callback)
}
}
}
当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)、和REMOVE_NODE(删除)。