Cuando comienzas tu carrera de programación, cavar en el código fuente de bibliotecas y marcos abiertos puede parecer un poco aterrador. En este artículo, Karl Mungazi comparte su experiencia de cómo superó su miedo y comenzó a usar el código fuente para adquirir conocimientos y desarrollar habilidades. También usa Redux para mostrar cómo "analiza" la biblioteca.¿Recuerdas cuando te sumergiste por primera vez en el código de una biblioteca o marco que usas con frecuencia? En mi vida, este momento llegó en mi primer trabajo como desarrollador front-end hace tres años.
Simplemente reescribimos un marco propietario obsoleto que se utilizó para crear cursos de capacitación interactivos. Al comienzo del trabajo de reescritura, analizamos algunas soluciones llave en mano, incluidas Mithril, Inferno, Angular, React, Aurelia, Vue y Polymer. Como todavía era un joven Padawan (que acababa de pasar del periodismo al desarrollo web), tenía mucho miedo a la complejidad de cada marco y la falta de comprensión de cómo funcionan.
La comprensión comenzó a llegar cuando comencé a explorar cuidadosamente el marco de Mithril. Desde entonces, mi conocimiento de JavaScript, y de la programación en general, se ha fortalecido significativamente gracias a las horas dedicadas a excavar en las partes internas de la biblioteca, que utilizo a diario en el trabajo y en mis propios proyectos. En este artículo te diré cómo puedes usar tu biblioteca favorita como tutorial
Empecé a leer el código fuente con la función hiperscript de MithrilVentajas de analizar el código fuente
Una de las principales ventajas de analizar el código fuente es que puedes aprender mucho. Cuando comencé a analizar el código Mithril, tenía una idea muy pobre de lo que era el DOM virtual. Cuando terminé, ya sabía que el DOM virtual es una técnica que implica la creación de un árbol de objetos que describe la interfaz de usuario. Este árbol se puede convertir a elementos DOM utilizando una API DOM como document.createElement. Para actualizar, se crea un nuevo árbol que describe el estado futuro de la interfaz y luego se compara con la versión anterior de este árbol.
Leí sobre esto en muchos artículos y manuales, pero lo más instructivo fue observar todo esto mientras trabajaba en nuestra aplicación. También aprendí a hacer las preguntas correctas al comparar marcos. En lugar de comparar clasificaciones, por ejemplo, puede hacer la pregunta "¿De qué manera la forma en que funciona este marco con los cambios afecta el rendimiento y la conveniencia del usuario final?"
Otra ventaja es el desarrollo de una buena arquitectura de aplicaciones. A pesar de que la mayoría de los proyectos de código abierto son generalmente más o menos similares en estructura a sus repositorios, todavía tienen diferencias. La estructura de Mithril es muy plana y si conoce bien su API, puede hacer suposiciones bastante realistas sobre el código en las carpetas de renderizado, enrutador y solicitud. La estructura de React, por otro lado, refleja su nueva arquitectura. Los desarrolladores separaron el módulo responsable de actualizar la IU (react-reconciler) del módulo responsable de representar los elementos DOM (react-dom).
Una de las ventajas de esta separación para los desarrolladores es que pueden escribir sus
propios renderizadores utilizando ganchos en el reactivo reconciliador. Parcel, el generador de módulos que estudié recientemente, también tiene una carpeta de paquetes, al igual que React. El módulo clave se llama paquete de paquetes, contiene código que se encarga de crear ensamblados, el funcionamiento del servidor de actualización del módulo (servidor de módulo dinámico) y la herramienta de línea de comandos.
Analizar el código fuente pronto te lleva a leer las especificaciones de JavaScript.Otra ventaja, que fue una gran sorpresa para mí, es que te facilita la lectura de la especificación oficial de JavaScript. La primera vez que me volví hacia ella cuando estaba tratando de descubrir cuál es la diferencia entre lanzar Error y lanzar un nuevo Error (spoiler -
nada ). Hice esta pregunta porque Mithril usó throw Error en la implementación de la función m y me pregunté por qué era mejor que lanzar un nuevo error. Luego también aprendí que los operadores && y ||
no necesariamente devuelve valores booleanos , encontré las
reglas por las cuales el operador de comparación no estricta == "resuelve" los valores y la
razón por la cual Object.prototype.toString.call ({}) devuelve '[objeto Object]'.
Cómo analizar el código fuente
Hay muchas formas de analizar el código fuente. La forma más fácil me parece lo siguiente: seleccione un método de su biblioteca y describa lo que sucede cuando lo llama. No vale la pena describir cada paso, solo necesita tratar de comprender sus principios y estructura generales.
Recientemente, analicé ReactDOM.render de esta manera y aprendí mucho sobre React Fiber y algunas de las dificultades para implementarlo. Afortunadamente, React es muy popular y la presencia de una gran cantidad de artículos sobre el mismo tema de otros desarrolladores aceleró el proceso.
Esta inmersión en el código también me presentó el concepto de
programación cooperativa , el método
window.requestIdleCallback y un
ejemplo en vivo
de una lista vinculada (React procesa las actualizaciones enviándolas a la cola, que es una lista vinculada prioritaria de actualizaciones). En el proceso, sería bueno crear una aplicación simple usando la biblioteca. Esto facilita la depuración ya que no tiene que lidiar con el seguimiento de la pila de otras bibliotecas.
Si no hago una revisión detallada, abriré la carpeta node_modules en el proyecto en el que estoy trabajando o echaré un vistazo a GitHub. Siempre hago esto cuando me encuentro con un error o una característica interesante. Al leer el código en GitHub, asegúrese de que esta sea la última versión. El código de la última versión se puede ver haciendo clic en el botón para cambiar ramas y seleccionando "etiquetas". Los cambios en las bibliotecas y los marcos están en curso, por lo que es poco probable que desee analizar algo que podría no estar en la próxima versión.
Una versión más superficial del código fuente de aprendizaje es lo que yo llamo un "vistazo rápido". De alguna manera instalé express.js, abrí la carpeta node_modules y revisé las dependencias. Si README no me dio una explicación satisfactoria, leí la fuente. Esto me llevó a descubrimientos interesantes:
- Express utiliza dos módulos para fusionar objetos, y el funcionamiento de estos módulos es muy diferente. merge-descriptors solo agrega las propiedades encontradas en el objeto fuente, y también agrega propiedades no enumerables, mientras que utils-merge repasa las propiedades enumeradas del objeto y toda su cadena de prototipos. merge-descriptors usa Object.getOwnPropertyNames () y Object.getOwnPropertyDescriptor (), y utils-merge usa para..in;
- El módulo setprototypeof proporciona una opción multiplataforma para especificar el prototipo del objeto creado (instanciado);
- escape-html es un módulo de escape de cadena de 78 líneas, después del cual el contenido se puede insertar en HTML;
Aunque es muy probable que estos descubrimientos no sean útiles de inmediato, una comprensión general de las dependencias de su biblioteca o marco es muy útil.
Las herramientas de depuración del navegador son sus mejores amigos al depurar código en la interfaz. Entre otras cosas, le permiten detener el programa en cualquier momento y verificar al mismo tiempo su estado, omitir la función o entrar o salir de ella. En el código minimizado, esto no es posible; es por eso que descomprimo este código y lo pongo en el archivo correspondiente en la carpeta node_modules.
Use el depurador como una aplicación útil. Haga una suposición y luego pruébela.Estudio de caso: función de conexión en Redux
React-Redux es una biblioteca para administrar el estado de las aplicaciones React. Cuando trabajo con bibliotecas populares como esta, empiezo buscando artículos sobre su uso. Al preparar este ejemplo, revisé este
artículo . Este es otro punto positivo en el aprendizaje del código fuente: lo lleva a artículos informativos como este que mejoran su pensamiento y comprensión.
Connect es una función react-redux que vincula el componente react y el almacén redux de una aplicación. Como? Según la
documentación, ella hace lo siguiente:
"... devuelve una nueva clase de componente relacionada, que es un contenedor del componente que se le pasó".
Después de leer esto, hago las siguientes preguntas:
- ¿Conozco patrones o conceptos donde las funciones devuelven parámetros de entrada envueltos con funcionalidades adicionales?
- Si es así, ¿cómo uso esto basado en la descripción de la documentación?
Por lo general, el siguiente paso es crear una aplicación primitiva usando la función de conexión. Sin embargo, en esta situación, utilicé una nueva aplicación en React, en la que trabajamos, porque quería entender la conexión en el contexto de una aplicación que muy probablemente llegaría pronto a producción.
El componente en el que me concentré se parece a esto:
class MarketContainer extends Component {
Este es un componente contenedor que sirve como envoltorio para los cuatro componentes relacionados más pequeños. Una de las primeras cosas que encuentra en el
archivo que conecta las exportaciones es el comentario "conectar es la fachada de connectAdvanced". Ya en esta etapa podemos aprender algo: tenemos la oportunidad de observar el patrón de "fachada" en acción. Al final del archivo, vemos conectar exportando una llamada a la función createConnect. Sus parámetros son un conjunto de valores predeterminados que se desestructuran de la siguiente manera:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {})
Y tenemos un momento más instructivo: exportación de la función llamada y desestructuración de los argumentos de la función por defecto. La reestructuración es instructiva para nosotros porque el código podría escribirse así:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory })
Como resultado, obtendríamos un error: Error de tipo no capturado: no se puede desestructurar la propiedad 'connectHOC' de 'undefined' o 'null'. Esto sucedería porque la función no tiene valores de argumento predeterminados.
Nota: para comprender mejor la reestructuración de los argumentos, puede leer el artículo de David Walsh . Algunos puntos pueden parecer triviales, dependiendo de su conocimiento del idioma, entonces puede enfocarse en aquellos puntos con los que no está familiarizado.La función createConnect en sí misma no hace nada. Simplemente devuelve la función de conexión que utilicé aquí:
export default connect(null, mapDispatchToProps)(MarketContainer)
Se necesitan cuatro argumentos opcionales y los tres primeros pasan por la función de
coincidencia , lo que ayuda a determinar su comportamiento en función de los argumentos que se pasan, así como su tipo. Resulta que, dado que el segundo argumento pasado para coincidir es una de las tres funciones importadas en connect, necesito elegir a dónde ir después.
También hay algo que aprender de la
función proxy utilizada para ajustar el primer argumento en connect, si estos argumentos son funciones; desde la utilidad
isPlainObject utilizada para verificar objetos simples o desde el módulo de
advertencia , que muestra cómo puede hacer un depurador que rompa todos los errores. Después de la función de coincidencia, pasamos a connectHOC, la función que toma nuestro componente de reacción y lo asocia con redux. Hay otra llamada de función que devuelve
wrapWithConnect , una función que realmente maneja el enlace del componente al repositorio.
Al observar la implementación de connectHOC, puedo adivinar por qué los detalles de la implementación de Connect deberían estar ocultos. Este es esencialmente el corazón de react-redux y contiene una lógica a la que no debería accederse a través de connect. Incluso si nos detenemos en esto, luego, si necesitamos profundizar más, ya tendremos el material fuente con una explicación detallada del código.
Resumir
Aprender el código fuente es muy complicado al principio. Pero, como todo lo demás, se vuelve más fácil con el tiempo. Su tarea no es comprender todo, sino sacar algo útil para sí mismo: una comprensión común y un nuevo conocimiento. Es muy importante tener cuidado durante todo el proceso y profundizar en los detalles.
Por ejemplo, encontré la función isPlainObject interesante porque usa esto if (typeof obj! == 'object' || obj === null) return false para asegurar que el argumento pasado sea un objeto simple. Cuando leí este código por primera vez, pensé, ¿por qué no usar Object.prototype.toString.call (opts)! == '[object Object]', lo que reduciría el código y separaría los objetos de sus subtipos como Date. Pero ya en la siguiente línea está claro que incluso si de repente (¡de repente!) Un desarrollador que usa connect devuelve un objeto Date, por ejemplo, al verificar Object.getPrototypeOf (obj) === null puede manejar esto.
Otro punto inesperado en esPlainObject en este lugar:
while (Object.getPrototypeOf(baseProto) !== null) { baseProto = Object.getPrototypeOf(baseProto) }
Encontrar una respuesta en Google me llevó a
este hilo en StackOverflow, y a
este comentario en Redux de GitHub, que explica cómo este código maneja situaciones en las que, por ejemplo, un objeto se transfiere desde un iFrame.
-
Primero decidí traducir el artículo. Agradecería aclaraciones, consejos y recomendaciones.