Всем привет и с наступающим новым годом! Сегодня я открыл исходный кодРеагировать проект. Хотя этот проект небольшой, он полон внутренних органов.
Давайте сначала представим стек технологий этого проекта:
- Сегмент семейства React: React 16 + Redux + React-router 4.0 + Immutable.js
- Синтаксис ES6 + ES7
- Сетевой запрос: Axios + Socket.io
- Фреймворк пользовательского интерфейса: Antd-mobile
- Бэкэнд: Экспресс + MongoDB
что такое Реакт
На самом деле React — это всего лишь инфраструктура пользовательского интерфейса, а частые операции с DOM обходятся дорого, поэтому React использует технологию виртуального DOM.Всякий раз, когда состояние изменяется, новый виртуальный DOM будет сгенерирован и изменен с исходным, так что измененное место для рендеринга. И по соображениям производительности выполняются только неглубокие сравнения состояний (это большой момент оптимизации).
Сегодня React стал одним из самых популярных фреймворков, но его изучение недешево и требует наличия хорошей основы JS. Поскольку React — это всего лишь фреймворк пользовательского интерфейса, если вы хотите завершить проект, вам придется использовать его семейную корзину, что увеличивает стоимость обучения. Таким образом, этот курс также предназначен для начинающих, чтобы новички могли быстро начать работу с React.
Реагировать компоненты
То, как вы пишете и планируете компонент, определяет, насколько хорошо ваш React может играть. Для компонента необходимо учитывать, предоставляет ли он несколько внешних интерфейсов, изменяется ли внутреннее состояние локальным состоянием или глобальным состоянием. И ваши компоненты должны быть многоразовыми и ремонтопригодными.
жизненный цикл компонента
-
render
Функция будет вызываться при рендеринге пользовательского интерфейса, и вы будете вызываться несколько раз при многократном рендеринге, поэтому контроль повторного рендеринга компонента важен для оптимизации производительности. -
componentDidMount
Функция будет вызываться только один раз после рендеринга компонента, обычно при запросе данных. -
shouldComponentUpdate
Это очень важная функция, ее возвращаемое значение определяет, нужно ли генерировать новый виртуальный DOM для сравнения с предыдущим. Часто встречающиеся проблемы с производительностью, вы можете получить хорошее решение здесь -
componentWillMount
Функция будет вызываться, когда компонент будет уничтожен, эта функция используется в проекте для очистки непрочитанных сообщений в чате.
Передача параметров родительско-дочернего компонента
То, как я использую в проекте, - это родительский компонент верхнего уровня с одним модулем черезconnect
Общайтесь с Redux. Подкомпоненты получают необходимые параметры через передачу параметров.У нас должны быть хорошие правила для типов параметров, чтобы облегчить последующую отладку.
С точки зрения производительности мы стараемся передавать только необходимые параметры в процессе передачи параметров.
маршрутизация
В React-router версии 4.0 официалы также выбрали путь компонентов для записи маршрутов.
Далее представлены высокоуровневые компоненты маршрутизации загрузки по запросу, используемые в проекте.
import React, { Component } from "react";
// 其实高阶组件就是一个组件通过参数传递的方式生成新的组件
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
// 存储组件
this.state = {
component: null
};
}
async componentDidMount() {
// 引入组件是需要下载文件的,所以是个异步操作
const { default: component } = await importComponent();
this.setState({
component: component
});
}
// 渲染时候判断文件下完没有,下完了就渲染出来
render() {
const C = this.state.component;
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
Redux
Redux часто сбивает с толку новичков. Во-первых, не в каждом проекте нужно использовать Redux, связи между компонентами не так много, а логика не сложная, поэтому вам не нужно использовать эту библиотеку, ведь стоимость разработки с использованием этой библиотеки очень высока. высокий.
Redux отделен от React, поэтому, если вы хотите общаться с Redux, вам нужно использовать React-redux, а если вы используете асинхронные запросы в действиях, вам нужно использовать Redux-thunk, потому что действия поддерживают только синхронные операции.
Состав Редукса
Redux состоит из трех частей: действия, хранилища и редуктора.
Действие, как следует из названия, заключается в том, что вы инициируете действие, которое используется следующим образом:
export function getOrderSuccess(data) {
// 返回的就是一个 action,除了第一个参数一般这样写,其余的参数名随意
return { type: GET_ORDER_SUCCESS, payload: data };
}
После того, как действие отправлено, оно будет передано редюсеру. Редьюсер — это чистая функция (функция, которая не зависит и не изменяет состояние переменных за пределами своей области), которая принимает предыдущее состояние и параметры действия и возвращает новое состояние в хранилище.
export default function(state = initialState, action) {
switch (action.type) {
case GET_ALL_ORDERS:
return state.set("allOrders", action.payload);
default:
break;
}
return state;
}
Store легко спутать с состоянием. Вы можете думать о Store как о контейнере, в котором хранится состояние. Магазин предоставляет некоторые API, которые позволяют вам получать доступ к состоянию, изменять его и т. д.
PS: состояние разрешено изменять только в редьюсерах.
Объяснив эти основные понятия, я подумал, что пришло время немного углубиться в Redux.
Реализуйте Redux самостоятельно
Я уже говорил, что Store — это контейнер, поэтому вы можете написать следующий код
class Store {
constructor() {}
// 以下两个都是 store 的常用 API
dispatch() {}
subscribe() {}
}
Store хранит состояние и может получить доступ к значению состояния в любое время, тогда вы можете написать следующий код
class Store {
constructor(initState) {
// _ 代表私有,当然不是真的私有,便于教学就这样写了
this._state = initState
}
getState() {
return this._state
}
// 以下两个都是 store 的常用 API
dispatch() {}
subscribe() {}
}
Далее рассмотрим логику отправки. Первая отправка должна получить параметр действия и отправить его редюсеру для обновления состояния. Затем, если пользователь подписывается на состояние, мы также должны вызвать функцию, тогда мы можем написать следующий код
dispatch(action) {
this._state = this.reducer(this.state, action)
this.subscribers.forEach(fn => fn(this.getState()))
}
Логика редуктора очень проста, вы можете сохранить редюсер в конструкторе, тогда вы можете написать следующий код
constructor(initState, reducer) {
this._state = initState
this._reducer = reducer
}
Теперь, когда простой полуфабрикат Redux завершен, мы можем выполнить следующий код
const initState = {value: 0}
function reducer(state = initState, action) {
switch (action.type) {
case 'increase':
return {...state, value: state.value + 1}
case 'decrease': {
return {...state, value: state.value - 1}
}
}
return state
}
const store = new Store(initState, reducer)
store.dispatch({type: 'increase'})
console.log(store.getState()); // -> 1
store.dispatch({type: 'increase'})
console.log(store.getState()); // -> 2
Последний шаг позволяет нам завершить функцию подписки, функция подписки вызывается следующим образом
store.subscribe(() =>
console.log(store.getState())
)
Таким образом, функция подписки должна получить параметр функции, поместить параметр функции в массив и вызвать функцию.
subscribe(fn) {
this.subscribers = [...this.subscribers, fn];
fn(this.value);
}
constructor(initState, reducer) {
this._state = initState
this._reducer = reducer
this.subscribers = []
}
С тех пор простая внутренняя логика Redux была завершена, и вы можете запустить код, чтобы попробовать его.
В ходе курса я объясню реализацию промежуточного программного обеспечения Redux, поэтому сначала я опишу его здесь. Благодаря этому анализу я считаю, что Redux не должен никого смущать.
Immutable.js
Я использовал эту библиотеку в этом проекте, вы можете увидеть проект для конкретного использования, вот что решает библиотека.
Во-первых, все объекты JS являются ссылочными отношениями.Конечно, вы можете глубоко скопировать объект, но эта операция довольно требовательна к производительности для сложных структур данных.
Immutable был создан, чтобы решить эту проблему. Типы данных этой библиотеки являются неизменяемыми, когда вы захотите изменить данные в ней, она клонирует узел и его родительский узел, поэтому операция достаточно эффективна.
Преимущества этой библиотеки значительны: - Предотвращает проблемы с асинхронной безопасностью - Высокая производительность и отличная помощь в оптимизации рендеринга React. - мощный синтаксический сахар - Время и космический шаттл (то есть отменить восстановление)
Конечно, есть и некоторые недостатки: - Проект слишком навязчив (не рекомендуется для старых проектов) - есть стоимость обучения - Часто забывают переназначить. . .
Использование Immutable.js также рассматривается в видео.
оптимизация производительности
- Сократите ненужное время рендеринга
- Используйте хорошие структуры данных
- Кэширование данных с помощью Reselect
Как добиться оптимизации производительности, мы обсудим позже в курсе.
связанный с чатом
Для функции чата я использую библиотеку Socket.io. Библиотека будет использовать Websocket в поддерживаемых браузерах и понизить версию для использования других протоколов, если нет.
Протокол TCP используется в Websocket.В производственной среде, теоретически, для длительного TCP-соединения необходимо только убедиться, что сервер получает сообщение и отвечает ACK.
В структуре базы данных чатов этого проекта я сохраняю каждый чат как документ, поэтому в будущем мне нужно будет только отправлять сообщения в поле сообщений этого документа.
const chatSchema = new Schema({
messageId: String,
// 聊天双方
bothSide: [
{
user: {
type: Schema.Types.ObjectId
},
name: {
type: String
},
lastId: {
type: String
}
}
],
messages: [
{
// 发送方
from: {
type: Schema.Types.ObjectId,
ref: "user"
},
// 接收方
to: {
type: Schema.Types.ObjectId,
ref: "user"
},
// 发送的消息
message: String,
// 发送日期
date: { type: Date, default: Date.now }
}
]
});
// 聊天具体后端逻辑
module.exports = function() {
io.on("connection", function(client) {
// 将用户存储一起
client.on("user", user => {
clients[user] = client.id;
client.user = user;
});
// 断开连接清除用户信息
client.on("disconnect", () => {
if (client.user) {
delete clients[client.user];
}
});
// 发送聊天对象昵称
client.on("getUserName", id => {
User.findOne({ _id: id }, (error, user) => {
if (user) {
client.emit("userName", user.user);
} else {
client.emit("serverError", { errorMsg: "找不到该用户" });
}
});
});
// 接收信息
client.on("sendMessage", data => {
const { from, to, message } = data;
const messageId = [from, to].sort().join("");
const obj = {
from,
to,
message,
date: Date()
};
// 异步操作,找到聊天双方
async.parallel(
[
function(callback) {
User.findOne({ _id: from }, (error, user) => {
if (error || !user) {
callback(error, null);
}
callback(null, { from: user.user });
});
},
function(callback) {
User.findOne({ _id: to }, (error, user) => {
if (error || !user) {
callback(error, null);
}
callback(null, { to: user.user });
});
}
],
function(err, results) {
if (err) {
client.emit("error", { errorMsg: "找不到聊天对象" });
} else {
// 寻找该 messageId 是否存在
Chat.findOne({
messageId
}).exec(function(err, doc) {
// 不存在就自己创建保存
if (!doc) {
var chatModel = new Chat({
messageId,
bothSide: [
{
user: from,
name: results[0].hasOwnProperty("from")
? results[0].from
: results[1].from
},
{
user: to,
name: results[0].hasOwnProperty("to")
? results[0].to
: results[1].to
}
],
messages: [obj]
});
chatModel.save(function(err, chat) {
if (err || !chat) {
client.emit("serverError", { errorMsg: "后端出错" });
}
if (clients[to]) {
// 该 messageId 不存在就得发送发送方昵称
io.to(clients[to]).emit("message", {
obj: chat.messages[chat.messages.length - 1],
name: results[0].hasOwnProperty("from")
? results[0].from
: results[1].from
});
}
});
} else {
doc.messages.push(obj);
doc.save(function(err, chat) {
if (err || !chat) {
client.emit("serverError", { errorMsg: "后端出错" });
}
if (clients[to]) {
io.to(clients[to]).emit("message", {
obj: chat.messages[chat.messages.length - 1]
});
}
});
}
});
}
}
);
});
});
};
Этой функции в курсе будет уделено особое внимание, и будет открыто отдельное видео для пояснения необходимых знаний о прикладном уровне и транспортном уровне.
Связанные с курсом
Ожидается, что видео будет длиться более 20 часов, но ведь я не штатный лектор, а фронтовой разработчик, поэтому я буду обновлять только 2-3 часа видео в неделю, а видео будет обновляйтесь в группе по мере возможности.
Поскольку все были настолько воодушевлены, менее чем за несколько дней было добавлено более 600 человек, поэтому была открыта подписная учетная запись для публикации обновлений видео.
Наконец
Этоадрес проекта, вы можете дать мне звезду, если считаете, что это хорошо.
Эта статья также является моим первым блогом за 18 лет, я желаю всем счастливого нового года и получить больше знаний в новом году!