您现在的位置是:首页 >学无止境 >20230531----重返学习-redux总步骤-TaskOA-react路由管理方案react-router-dom网站首页学无止境
20230531----重返学习-redux总步骤-TaskOA-react路由管理方案react-router-dom
day-082-eighty-two-20230531-redux总步骤-TaskOA-react路由管理方案react-router-dom
redux总步骤
-
确定基础骨架目录。
fang/f20230531/src/store/index.js
fang/f20230531/src/store/reducers/
fang/f20230531/src/store/reducers/index.js
fang/f20230531/src/store/reducers/demoReducer.js
fang/f20230531/src/store/reducers/taskReducer.js
fang/f20230531/src/store/action-types.js
fang/f20230531/src/store/actions/
fang/f20230531/src/store/actions/index.js
fang/f20230531/src/store/actions/demoAction.js
fang/f20230531/src/store/actions/taskAction.js
-
编写基础骨架代码。
-
定义派发标识。
-
fang/f20230531/src/store/action-types.js
定义派发标识。export const DEMO_INCREMENT = `DEMO_INCREMENT`
-
-
创建基础reducer骨架。
-
fang/f20230531/src/store/reducers/demoReducer.js
创建基础reducer骨架。import * as AT from '../action-types' let initial={ } export default function demoReducer(state=initial,action){ state={...state} switch(action.type){ //... default: } return state }
-
fang/f20230531/src/store/reducers/taskReducer.js
创建基础reducer骨架。import * as AT from '../action-types' let initial={ } export default function taskReducer(state=initial,action){ state={...state} switch(action.type){ //... default: } return state }
-
-
编写具体reducer业务逻辑。
-
fang/f20230531/src/store/reducers/demoReducer.js
编写具体reducer业务逻辑。import * as AT from "../action-types"; let initial = { num: 0, }; export default function demoReducer(state = initial, action) { state = { ...state }; let { type, step = 1 } = action; switch (type) { //... case AT.DEMO_INCREMENT: state.num += Number(step); break; default: } return state; } /* // 期望派发; dispatch({ type:'DEMO_INCREMENT', step:10, }) */
-
-
合并reducer并导出。
-
fang/f20230531/src/store/reducers/index.js
合并reducer并导出。import {combineReducers} from 'redux' import demoReducer from './demoReducer' import taskReducer from './taskReducer' const reducer = combineReducers({ demo:demoReducer, task:taskReducer, }) export default reducer
-
-
创建redux公共容器。
-
fang/f20230531/src/store/index.js
创建redux公共容器。import { legacy_createStore } from "redux"; import reducer from './reducers' const store = legacy_createStore(reducer) export default store
-
-
创建action的基础骨架。
-
fang/f20230531/src/store/actions/demoAction.js
创建action的基础骨架。import * as AT from '../action-types' const demoAction = { } export default demoAction
-
fang/f20230531/src/store/actions/taskAction.js
创建action的基础骨架。import * as AT from '../action-types' const taskAction = { } export default taskAction
-
-
修改具体action的派发逻辑。
-
fang/f20230531/src/store/actions/demoAction.js
修改具体action的派发逻辑。import * as AT from '../action-types' const demoAction = { increment(step=1){ return { type:AT.DEMO_INCREMENT, step, } } } export default demoAction
-
-
合并action并导出。
-
fang/f20230531/src/store/actions/index.js
合并action并导出。import demoAction from "./demoAction"; import taskAction from "./taskAction"; const action = { demo:demoAction, task:taskAction, } export default action
-
-
-
使用react-redux把公共状态容器放到根组件上下文中。
-
fang/f20230531/src/index.jsx
根组件创建上下文对象,并注入公共状态容器。// redux import store from './store' import {Provider} from 'react-redux' root.render( <Provider store={store}> //.... </Provider> )
import React from 'react' import ReactDOM from 'react-dom/client' /* ANTD */ import { ConfigProvider } from 'antd' import dayjs from 'dayjs' import zhCN from 'antd/locale/zh_CN' import 'dayjs/locale/zh-cn' /* 组件&样式 */ import './index.less' import Demo from './views/Demo' // redux import store from './store' import {Provider} from 'react-redux' dayjs.locale('zh-cn') const root = ReactDOM.createRoot(document.getElementById('root')) root.render( <ConfigProvider locale={zhCN}> <Provider store={store}> <Demo /> </Provider> </ConfigProvider> )
-
-
业务组件从上下文对象中获取或使用公共状态容器中的公共状态。
-
fang/f20230531/src/views/Demo.jsx
单个业务组件使用。const Demo = function Demo(props) { let { num, increment } = props; }; export default connect((state) => state.demo, action.demo)(Demo);
import React from "react"; import { Button } from "antd"; import styled from "styled-components"; import { connect } from "react-redux"; import action from "@/store/actions"; // 组件样式 const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 10px 20px; width: 200px; border: 1px solid #ddd; & > span { display: block; line-height: 35px; font-size: 18px; } `; const Demo = function Demo(props) { let { num, increment } = props; return ( <DemoStyle> <span>{num}</span> <Button type="primary" onClick={() => { increment(10); }} > 新增 </Button> </DemoStyle> ); }; // export default Demo; export default connect((state) => state.demo, action.demo)(Demo);
-
react-redux的数据流转
-
react-redux没有使用中间件的数据流转图:
-
react-redux使用中间件后的数据流转图:
- 实际流程中,并不一定是这样的,这里是为了好理解。中间件会跳来跳去。
- 中间件流程中,可能会有多个中间件插件,中间件插件会自上而下执行,之后才是到reducer。
- 中间件种类:
- redux-logger 输出派发日志。
- redux-thunk 处理异步派发的。
- redux-
-
fang/f20230531/src/store/index.js
简单使用中间件-打印日志。
import { legacy_createStore, applyMiddleware } from "redux";
import reduxLogger from "redux-logger";//输出派发日志---方便调试。
import reduxThunk from "redux-thunk";//输出派发日志。
import reduxPromise from "redux-promise";//输出派发日志。
import reducer from "./reducers";
const store = legacy_createStore(reducer, applyMiddleware(reduxLogger));
export default store;
fang/f20230531/src/store/index.js
分环境。
import { legacy_createStore, applyMiddleware } from "redux";
import reducer from "./reducers";
import reduxLogger from "redux-logger"; //输出派发日志---方便调试。
const env = process.env.NODE_ENV;
let middleware = [];
// 非生产环境下使用的中间件。
if (env !== "production") {
middleware.push(reduxLogger);
}
const store = legacy_createStore(reducer, applyMiddleware(...middleware));
export default store;
-
处理异步派发- 基于redux-promise。
-
fang/f20230531/src/store/index.js
import { legacy_createStore, applyMiddleware } from "redux"; import reducer from "./reducers"; import reduxLogger from "redux-logger"; //输出派发日志---方便调试。 import reduxThunk from "redux-thunk"; //输出派发日志。 import reduxPromise from "redux-promise"; //输出派发日志。 const env = process.env.NODE_ENV; let middleware = []; // let middleware = [reduxPromise]; // 非生产环境下使用的中间件。 if (env !== "production") { middleware.push(reduxLogger); } middleware.push(reduxPromise) const store = legacy_createStore(reducer, applyMiddleware(...middleware)); export default store;
-
fang/f20230531/src/store/actions/demoAction.js
import * as AT from '../action-types' import API from '@/api' const demoAction = { /* // 基于redux-promise实现异步派发。默认返回的是Promise实例。dispatch()执行时,会报错`Actions must be plain objects.`; 当我们应用了redux-promise中间件,点击按钮的时候,是基于其重写的dispatch实现派发的。 @1. dispatch(action.demo.increment(10)) - dispatch是重写的函数。 - action.demo.increment执行的结果是一个promise实例。 - 在reduxPromise中间件的内部会监测promise实例的结果。 @2. 如果实例是失败态,则不做任何处理!如果实例是成功的: - 在中间件的内部,再基于store.dispatch进行派发 - store.dispatch(promise实例的值-也就是函数返回的action对象) - 而此派发会通知reducer执行,以此来修改状态。 */ async increment(step=1){ await API.delay() return { type:AT.DEMO_INCREMENT, step, } } /* // 同步派发。 increment(step=1){ return { type:AT.DEMO_INCREMENT, step, } } */ } export default demoAction
-
基于redux-promise实现异步派发。
- 当我们应用了redux-promise中间件,点击按钮的时候,是基于其重写的dispatch实现派发的。
- dispatch(action.demo.increment(10))
- dispatch是重写的函数。
- action.demo.increment执行的结果是一个promise实例。
- 在reduxPromise中间件的内部会监测promise实例的结果。
- 如果实例是失败态,则不做任何处理!如果实例是成功的:
- 在中间件的内部,再基于store.dispatch进行派发
- store.dispatch(promise实例的值-也就是函数返回的action对象)
- 而此派发会通知reducer执行,以此来修改状态。
- 在中间件的内部,再基于store.dispatch进行派发
- dispatch(action.demo.increment(10))
- 当我们应用了redux-promise中间件,点击按钮的时候,是基于其重写的dispatch实现派发的。
-
-
处理异步派发- 基于redux-thunk。
-
fang/f20230531/src/store/index.js
import { legacy_createStore, applyMiddleware } from "redux"; import reducer from "./reducers"; import reduxLogger from "redux-logger"; //输出派发日志---方便调试。 import reduxThunk from "redux-thunk"; //可以执行异步派发任务-基于action中直接属性的同步函数返回的是一个函数-react官方推荐的,但不太好用。 import reduxPromise from "redux-promise"; //可以执行异步派发任务-基于action中直接属性的是一个异步函数-返回的是一个对象-使用起来更简单。 const env = process.env.NODE_ENV; let middleware = []; // let middleware = [reduxPromise]; // 非生产环境下使用的中间件。 if (env !== "production") { middleware.push(reduxLogger); } middleware.push(reduxPromise); //可以与"redux-thunk"一起使用。 middleware.push(reduxThunk); //可以与"redux-promise"一起使用。 const store = legacy_createStore( reducer, applyMiddleware(...middleware) //这里是处理中间间的,函数内部每个入参都是一个中间件,可以传入多个。用数组再解构,是更方便处理的。 ); export default store;
-
fang/f20230531/src/store/actions/demoAction.js
import * as AT from "../action-types"; import API from "@/api"; const demoAction = { /* // 基于redux-thunk中间件处理异步派发。 1. 也对dispatch进行了重写 - 单击按钮的时候,是基于重写的dispatch进行派发的。 - 执行action.demo.increment获取到的是一个函数。 - 之前返回出去的是一个用于store.dispatch()进行派发任务的行为对象。 - 现在是一个用于redux-thunk中间件内部执行的函数。 - redux-thunk中间件内部执行时,会传入store.dispatch作为第一个入参。即该函数的第一个形参就是表示store.dispatch。 - thunk中间件内部,会把获取的这个函数执行,并且把store.dispatch传递给这个函数。 - 即该函数的第一个形参就是表示store.dispatch。 2. 我们一般要在这个函数中发送异步请求,等待请求成功后,需要手动基于store.dispatch进行派发。 */ // 基于redux-thunk中间件处理异步派发。 increment(step = 1) { // 下方本来要返回一个对象的,但现在要进行异步,则改为返回一个函数。而redux-thunk中间件会执行该函数,并在执行时,传入store.dispatch。 return async (dispatch) => { // dispatch:store.dispatch。 await API.delay(); dispatch({ type: AT.DEMO_INCREMENT, step, }); }; }, /* // 基于redux-promise实现异步派发。默认返回的是Promise实例。dispatch()执行时,会报错`Actions must be plain objects.`; 当我们应用了redux-promise中间件,点击按钮的时候,是基于其重写的dispatch实现派发的。 @1. dispatch(action.demo.increment(10)) - dispatch是重写的函数。 - action.demo.increment执行的结果是一个promise实例。 - 在reduxPromise中间件的内部会监测promise实例的结果。 @2. 如果实例是失败态,则不做任何处理!如果实例是成功的: - 在中间件的内部,再基于store.dispatch进行派发 - store.dispatch(promise实例的值-也就是函数返回的action对象) - 而此派发会通知reducer执行,以此来修改状态。 */ /* // 基于redux-promise实现异步派发。 async increment(step=1){ await API.delay() return { type:AT.DEMO_INCREMENT, step, } } */ /* // 同步派发。 increment(step=1){ return { type:AT.DEMO_INCREMENT, step, } } */ }; export default demoAction;
-
基于redux-thunk中间件处理异步派发。
- 也对dispatch进行了重写。
- 单击按钮的时候,是基于重写的dispatch进行派发的。
- 执行action.demo.increment获取到的是一个函数。
- 之前返回出去的是一个用于store.dispatch()进行派发任务的行为对象。
- 现在是一个用于redux-thunk中间件内部执行的函数。
- redux-thunk中间件内部执行时,会传入store.dispatch作为第一个入参。即该函数的第一个形参就是表示store.dispatch。
- thunk中间件内部,会把获取的这个函数执行,并且把store.dispatch传递给这个函数。
- 即该函数的第一个形参就是表示store.dispatch。
- 我们一般要在这个函数中发送异步请求,等待请求成功后,需要手动基于store.dispatch进行派发。
- 也对dispatch进行了重写。
-
redux-logger源码
- redux-logger源码可以在node_modules中看到。
/node_modules/redux-logger/src/index.js
TaskOA
-
对于增删改查–减少服务器请求版思路:
- 查-发起数据查询请求,查询全部数据。
- 切换数据-根据全部数据筛选数据,用于页面渲染。
- 删-发送删除请求,修改本地总数据,不发起数据查询请求。
- 增-发送新增请求,发起数据查询请求。
- 改-发送修改请求,修改本地总数据,不发起数据查询请求。
- 查-发起数据查询请求,查询全部数据。
-
redux作用。
- 把数据缓存在本地,减少对服务器请求。想更新,让用户手动刷新一次。
- 公共状态管理,跨组件通信。
-
用redux修改旧页面流程:
-
组件第一次渲染的时候,我们首先判断redux容器中是否存储了“全部任务”
- 存储了:啥都不干
- 没存储:向服务器发送请求,异步获取全部的任务,然后存储到redux中
-
表格中需要呈现的数据,是依赖于 redux中的全部任务 和 选中的状态,筛选出来的!
- 只要redux容器中的全部任务变了 或者 选中状态变了,则重新筛选出最新的数据进行渲染!
-
点击新增:
- 发请求告诉服务器新增
- 如果新增成功,则派发任务,从服务器重新获取最新的全部任务,存储到redux中!
-
点击删除/完成
- 正常和服务器通信
- 派发任务,把redux中存储的全部任务中的某一条,进行删除和修改!
-
-
用redux修改旧页面的步骤:
-
fang/f20230531/src/store/action-types.js
export const DEMO_INCREMENT = `DEMO_INCREMENT` export const TAST_QUERY_ALL_lIST = `TAST_QUERY_ALL_lIST` export const TAST_REMOVE = `TAST_REMOVE` export const TAST_UPDQTE = `TAST_UPDQTE`
-
fang/f20230531/src/store/actions/taskAction.js
import * as AT from "../action-types"; import API from "@/api"; const taskAction = { // 获取全部任务的异步派发。 async queryAllList() { let list = []; try { let result = await API.queryTaskList(); if (+result.code === 0) { list = result.list; } } catch (error) { console.log(`error-->`, error); } // 返回派发的对象; return { type: AT.TAST_QUERY_ALL_lIST, list, }; }, // 删除任务的同步派发。 removeTask(id) { return { type: AT.TAST_REMOVE, id, }; }, // 修改任务的同步派发。 updateTask(id) { return { type: AT.TAST_UPDQTE, id, }; }, }; export default taskAction;
-
fang/f20230531/src/store/reducers/taskReducer.js
import * as AT from "../action-types"; let initial = { list: null, //null说明还没有从服务器获取过。空数组或其它数组,都表示已经从服务器获取到了。 }; export default function taskReducer(state = initial, action) { state = { ...state }; let { type, list, id } = action; switch (type) { //... case AT.TAST_QUERY_ALL_lIST: state.list = list; break; case AT.TAST_REMOVE: if (Array.isArray(state.list)) { state.list = state.list.filter((item) => +item.id !== +id); } break; case AT.TAST_UPDQTE: if (Array.isArray(state.list)) { state.list = state.list.map((item) => { if (+item.id === +id) { item.state = 2; item.complete = new Date().toLocaleString("zh-CN", { hour12: false, }); } return item; }); } break; default: } return state; }
-
fang/f20230531/src/views/Task.jsx
import { useState, useEffect, useMemo } from "react"; import styled from "styled-components"; import { connect } from "react-redux"; import action from "@/store/actions"; /* 组件样式 */ const TaskStyle = styled.div` box-sizing: border-box; `; /* 组件视图 */ const Task = function Task(props) { // 获取属性信息; let { list, queryAllList, removeTask, updateTask } = props; // 定义状态 let [loading, setLoading] = useState(false) // 组件第一次渲染完毕,先判断一下redux中是否存在全部任务,如果没有存在,则完成一次异步派发。 useEffect(() => { (async ()=>{ if (!list) { setLoading(true) // 默认派发一次。 let xxx = await queryAllList();//只有使用的是redux-promise中间件,才保证派发返回的结果是promise实例,才可以用await做监听。 console.log('xxx',xxx); setLoading(false) } })() }, []); // 依赖于redux中的全部任务-list,和当前选中的状态selectedIndex,筛选表格需要的数据。 let data = useMemo(() => { let arr = list || []; if (selectedIndex === 1 || selectedIndex === 2) { arr = arr.filter((item) => { return +item.state === selectedIndex; }); } return arr; }, [list, selectedIndex]); // 确认新增任务 const submit = async () => { setLoading(true) await queryAllList(); setLoading(false) }; // 删除任务 const handleRemove = async (id) => { removeTask(id) }; // 修改任务 const handleUpdate = async (id) => { updateTask(id) }; return ( <TaskStyle> //... </TaskStyle> ); }; export default connect((state) => state.task, action.task)(Task);
import { useState, useEffect, useMemo } from "react"; import styled from "styled-components"; import { Button, Tag, Table, Popconfirm, Modal, Form, Input, DatePicker, message, } from "antd"; import _ from "@/assets/utils"; import dayjs from "dayjs"; import API from "@/api"; import { connect } from "react-redux"; import action from "@/store/actions"; /* 组件样式 */ const TaskStyle = styled.div` box-sizing: border-box; margin: 0 auto; width: 800px; .header-box { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #ddd; .title { font-size: 20px; font-weight: normal; } } .tag-box { margin: 15px 0; .ant-tag { padding: 5px 15px; margin-right: 15px; font-size: 14px; cursor: pointer; } } .ant-btn-link { padding: 4px 5px; } .ant-modal-header { margin-bottom: 20px; } `; /* 组件视图 */ const Task = function Task(props) { // 获取属性信息; let { list, queryAllList, removeTask, updateTask } = props; // 定义表格列 const columns = [ { title: "编号", dataIndex: "id", width: "6%", align: "center", }, { title: "任务描述", dataIndex: "task", width: "50%", ellipsis: true, }, { title: "状态", dataIndex: "state", width: "10%", align: "center", render: (text) => (+text === 1 ? "未完成" : "已完成"), }, { title: "完成时间", width: "16%", align: "center", render(text, record) { let { state, time, complete } = record; time = +state === 1 ? time : complete; return _.formatTime(time, "{1}/{2} {3}:{4}"); }, }, { title: "操作", width: "18%", render(text, record) { let { state, id } = record; return ( <> <Popconfirm title="您确定要删除此任务吗?" onConfirm={handleRemove.bind(null, id)} > <Button type="link" danger> 删除 </Button> </Popconfirm> {+state === 1 ? ( <Popconfirm title="您确定要把此任务设置为已完成吗?" onConfirm={handleUpdate.bind(null, id)} > <Button type="link">完成</Button> </Popconfirm> ) : null} </> ); }, }, ]; // 定义状态 // let [data, setData] = useState([]), let [loading, setLoading] = useState(false), [selectedIndex, setSelectedIndex] = useState(0); let [modalVisible, setModalVisible] = useState(false), [confirmLoading, setConfirmLoading] = useState(false); let [formIns] = Form.useForm(); // 组件第一次渲染完毕,先判断一下redux中是否存在全部任务,如果没有存在,则完成一次异步派发。 useEffect(() => { (async ()=>{ if (!list) { setLoading(true) // 默认派发一次。 let xxx = await queryAllList();//只有使用的是redux-promise中间件,才保证派发返回的结果是promise实例,才可以用await做监听。 console.log('xxx',xxx); setLoading(false) } })() }, []); // 依赖于redux中的全部任务-list,和当前选中的状态selectedIndex,筛选表格需要的数据。 let data = useMemo(() => { let arr = list || []; if (selectedIndex === 1 || selectedIndex === 2) { arr = arr.filter((item) => { return +item.state === selectedIndex; }); } return arr; }, [list, selectedIndex]); // // 第一次渲染完&每一次选中状态改变,都要从服务器重新获取数据 // useEffect(() => { // initData(); // }, [selectedIndex]); // // 从服务器获取指定状态的任务 // const initData = async () => { // setLoading(true); // try { // let { code, list } = await API.queryTaskList(selectedIndex); // if (+code !== 0) list = []; // setData(list); // } catch (_) {} // setLoading(false); // }; // 关闭Modal对话框 const closeModal = () => { setModalVisible(false); setConfirmLoading(false); formIns.resetFields(); }; // 确认新增任务 const submit = async () => { try { await formIns.validateFields(); let { task, time } = formIns.getFieldsValue(); time = time.format("YYYY-MM-DD HH:mm:ss"); setConfirmLoading(true); let { code } = await API.insertTaskInfo(task, time); if (+code === 0) { message.success("恭喜您,新增成功~"); closeModal(); // initData(); // 新增成功后,需要派发异步任务,同步最新的数据到redux中。 setLoading(true) await queryAllList(); setLoading(false) } else { message.error("很遗憾,新增失败,请稍后再试~"); } } catch (_) {} setConfirmLoading(false); }; // 删除任务 const handleRemove = async (id) => { try { let { code } = await API.removeTaskById(id); if (+code === 0) { message.success("恭喜您,删除成功"); // let arr = data.filter((item) => +item.id !== +id); // setData(arr); // 派发删除的同步任务。 removeTask(id) return; } message.error("很遗憾,删除失败,请稍后再试"); } catch (_) {} }; // 修改任务 const handleUpdate = async (id) => { try { let { code } = await API.updateTaskById(id); if (+code === 0) { message.success("恭喜您,修改成功"); // let arr = data.map((item) => { // if (+item.id === +id) { // item.state = 2; // item.complete = dayjs().format("YYYY-MM-DD HH:mm:ss"); // } // return item; // }); // setData(arr); //派发修改的同步任务。 updateTask(id) return; } message.error("很遗憾,修改失败,请稍后再试"); } catch (_) {} }; return ( <TaskStyle> <div className="header-box"> <h2 className="title">TASK OA 任务管理系统</h2> <Button type="primary" onClick={() => setModalVisible(true)}> 新增任务 </Button> </div> <div className="tag-box"> {["全部", "未完成", "已完成"].map((item, index) => { return ( <Tag key={index} color={selectedIndex === index ? "#1677ff" : ""} onClick={() => { if (selectedIndex === index) return; setSelectedIndex(index); }} > {item} </Tag> ); })} </div> <Table columns={columns} dataSource={data} loading={loading} pagination={false} rowKey="id" size="middle" /> <Modal title="新增任务窗口" okText="确认提交" keyboard={false} maskClosable={false} getContainer={false} confirmLoading={confirmLoading} open={modalVisible} afterClose={closeModal} onCancel={closeModal} onOk={submit} > <Form colon={false} layout="vertical" validateTrigger="onBlur" form={formIns} initialValues={{ task: "", time: dayjs().add(1, "day"), }} > <Form.Item name="task" label="任务描述" rules={[{ required: true, message: "任务描述是必填项" }]} > <Input.TextArea rows={4} /> </Form.Item> <Form.Item name="time" label="预期完成时间" rules={[{ required: true, message: "请先选择预期完成时间" }]} > <DatePicker showTime /> </Form.Item> </Form> </Modal> </TaskStyle> ); }; export default connect((state) => state.task, action.task)(Task);
-
react路由管理方案react-router-dom
- web端用的是react-router-dom。原生用的是react-router。
- SPA:单页面应用。
- 一个项目只有一个页面,我们在这个页面中,基于前端路由机制,控制组件的显示和隐藏或销毁。
- 页面不更新,体验比较好。
- 一个项目只有一个页面,我们在这个页面中,基于前端路由机制,控制组件的显示和隐藏或销毁。
- MPA:多页面应用。
SEO优化
- 简单介绍SEO搜索引擎优化:
-
搜索引擎(例如:谷歌、必应bing、百度、360、…)都有一个工具:爬虫、小蜘蛛。
- 它们会按照相关的规则,去抓取与收录各个网站中的信息-即网页源代码中可以看到的信息,把信息收录到自己的搜索引擎库中。
- 根据收录的内容和相应的规则,计算出网站与关键词的权重。
- 当用户基于搜索引擎搜索某个关键词的时候,会去库中进行匹配,把匹配的内容呈现出来,而且权重越高的越靠前!
- 它们会按照相关的规则,去抓取与收录各个网站中的信息-即网页源代码中可以看到的信息,把信息收录到自己的搜索引擎库中。
-
SEO优化,就是尽可能提高网站的权重,并且让搜索引擎收录更多的信息。
<title>网易云音乐</title> <meta name="keywords" content="关键字" /> <meta name="description" content="" /> ....
- 这个title与keywords-content关键字与keywords-description网页描述是需要做的。
-
SEM竞价排名:看谁花钱多!
- 会有标识之类的东西,要花钱。
-
- 目前SEO优化已经不是主流的优化方案了!
- 如果网站是JavaScript动态渲染的,在其源代码中,是看到动态绑定的信息的!也就不利于SEO优化!
- 例如:
- 基于vue/react等框架开发的网站。
- 基于客户端渲染的网站。
- 例如:
- 想做SEO优化,不能基于客户端渲染,需要基于服务器进行渲染!
- 传统方式:前后端不分离-php、jsp…
- 新方案:SSR服务器渲染-Server Side Render服务器端渲染。
- vue:vue + nuxt.js + Node。
- react:react + next.js + Node。
- 前端工程师踏入全栈工程师的第一步。
路由设计模式
hash哈希路由
-
原理:每一次路由切换或组件切换,都是改变了页面的哈希值。
- 而哈希值的变化,不会引起页面的刷新!
- 我们可以基于hashchange事件,监听哈希值的变化,用最新的哈希值,去路由表中进行匹配,把匹配的组件拿过来渲染。
- 之前渲染的组件销毁。
-
代码示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>前端路由机制实现</title> </head> <body> <nav class="nav-box"> <a href="#/">首页</a> <a href="#/product">产品中心</a> <a href="#/personal">个人中心</a> </nav> <!-- view-box是用来匹配渲染的组件的。 --> <div class="view-box"></div> <!-- IMPORT JS --> <script> const viewBox = document.querySelector(".view-box"); // 构建路由表-什么哈希值,匹配什么组件/内容。 const routes = [ { path: "/", components: "首页的内容" }, { path: "/product", components: "产品中心的内容" }, { path: "/personal", components: "个人中心的内容" }, { path: "/me", redirect: "/personal" }, //重定向。 { path: "/my", alias: "/personal" }, //别名。 { path: "*", components: "404错误页面" }, //404页面。 ]; //监听哈希值的变化,根据最新的哈希值,和路由表进行匹配,把匹配的内容放在viewBox容器中渲染! const matchRoute = function matchRoute() { // console.log(`location.hash-->`, location.hash); let hash = location.hash.substring(1); let item = routes.find((item) => item.path === hash); // 重定向。 if(item?.redirect){ location.hash = `#${item.redirect}`; return } // 别名。 if(item?.alias){ item = routes.find((item1) => item1.path === item.alias); } if (!item) { // 没有找到匹配项,再去找path=`*`的。 item = routes.find((item) => item.path === `*`); } if (!item) { // 正常的没找到,*的也没找到。 viewBox.innerHTML = ``; return; } // 如果找到了,则把匹配的内容进行渲染! viewBox.innerHTML = item.components; }; window.addEventListener("hashchange", matchRoute); // 第一次进入页面,让页面的哈希值默认是首页的值`#/` location.hash = `#/`; matchRoute(); </script> </body> </html>
history浏览器路由
-
原理:利用H5中的historyAPI实现的,在History.prototype上提供了页面地址跳转的方法,基于这些方法跳转,页面是不会刷新的,只是改了URL地址。
- API-History
- 常见方法:
- pushState:跳转到新的地址,新增一条历史记录。
- replaceState:跳转到新的地址,替换现在的历史记录。
- forward 前进一步,只会在现在历史记录中跳转,不会新增历史记录。
- back 后退一步。
- go 跳转到指定的那一步。
- go(-1) --> back();
- go(1) --> forward();
-
当基于pushState与replaceState进行跳转的时候,我们会在跳转之后,执行路由匹配的方法。
-
当基于forward与back与go进行跳转的时候-也就是前进后退等操作,我们可以基于popstate事件,监听到路由跳转,然后执行路由匹配的跳转。
-
路由匹配的方法:
- 获取最新的地址,和路由表中的path进行匹配。
- 把匹配的内容或组件,放在指定的容器中渲染。
-
平时项目中使用何种方式:
- 最常用的是哈希路由!
- 只不过有人认为,哈希这种方式比较丑,而且改变的是哈希值,并不是地址,有点不真实!也不利于SEO优化!
- 而且History路由,必须让服务器给予支持才可以!因为我们路由跳转的地址是不存在的。页面不刷新还好,一旦刷新,则重新向服务器发送请求,此地址在服务器并不存在,服务器默认会返回404,这样肯定不行,我们需要服务器在访问地址不存在的情况下,返回客户端的依然是我们唯一的那个页面!
- SPA单页面应用,只有一个真实的页面,其余跳转到的地址,没有对应的其它页面。
- 最常用的是哈希路由!