Escribir una API para componentes React, parte 1: no cree accesorios conflictivos
Escribir una API para React Components, Parte 2: Dar nombres al comportamiento, no a la interacción
Escribir una API para componentes React, parte 3: el orden de los accesorios es importante
Escribir una API para React Components, Parte 4: ¡Cuidado con el Apropacalypse!
Escribir una API para React Components, Parte 5: solo use la composición
Escribimos API para componentes React, parte 6: creamos comunicación entre componentes
Comencemos con un componente React simple que muestra una etiqueta de anclaje:

<Link href="sid.studio">Click me</Link> // : <a href="sid.studio" class="link">Click me</a>
Así es como se ve el código del componente:
const Link = props => { return ( <a href={props.href} className="link"> {props.children} </a> ) }
También queremos poder agregar atributos html al elemento, como id
, target
, title
, data-attr
, etc.
Como hay muchos atributos HTML, podemos pasar todos los accesorios y agregar los que necesitamos (como className
)
(Nota: no debe pasar los atributos que se le ocurrieron para este componente que no están en la especificación HTML)
En este caso, simplemente puede usar className
const Link = props => { return <a {...props} className="link" /> }
Ahí es donde se pone interesante.
Todo parece estar bien cuando alguien pasa una id
o un target
:
<Link href="sid.studio" id="my-link">Click me</Link> // : <a href="sid.studio" id="my-link" class="link">Click me</a>
pero que pasa cuando alguien pasa className
?

<Link href="sid.studio" className="red-link">Click me</Link> // : <a href="sid.studio" class="link">Click me</a>
Bueno, no pasó nada. Reaccionar completamente ignorado la clase personalizada. Volvamos a la función:
const Link = props => { return <a {...props} className="link" /> }
Ok, imaginemos cómo compila este ...props
, el código anterior es equivalente a esto:
const Link = props => { return ( <a href="sid.studio" className="red-link" className="link" > Click me </a> ) }
¿Ves el conflicto? Hay dos className
. ¿Cómo maneja React esto?
Bueno, React no hace nada. Babel lo hace!
Recuerde que JSX "produce" React.createElement
. Los accesorios se convierten en un objeto y se pasan como argumento. Los objetos no admiten claves duplicadas, por lo que el segundo className
sobrescribirá el primero.
const Link = props => { return React.createElement( 'a', { className: 'link', href: 'sid.studio' }, 'Click me' ) }
Bien, ahora que sabemos sobre el problema, ¿cómo podemos resolverlo?
Es útil comprender que este error ocurrió debido a un conflicto de nombre, y esto puede suceder con cualquier accesorio, y no solo con className
. Por lo tanto, la decisión depende del comportamiento que desea implementar.
Hay tres escenarios posibles:
- Un desarrollador que use nuestro componente debería poder anular el valor de utilería predeterminado.
- No queremos dejar que el desarrollador cambie algunos accesorios
- El desarrollador debe poder agregar valores mientras mantiene el valor predeterminado.
Vamos a separarlos uno a la vez.
1. El desarrollador que usa nuestro componente debería poder anular el valor de apoyo predeterminado
Este es el comportamiento que generalmente espera de otros atributos: id
, title
, etc.
A menudo vemos la configuración de test id
en cosmos (el sistema de diseño en el que estoy trabajando). Cada componente recibe un data-test-id
predeterminado, a veces los desarrolladores desean agregar su propio identificador de prueba para indicar un uso específico.
Aquí hay uno de esos casos de uso:

const Breadcrumb = () => ( <div className="breadcrumb" data-test-id="breadcrumb"> <Link data-test-id="breadcrumb.link">Home</Link> <Link data-test-id="breadcrumb.link">Parent</Link> <Link data-test-id="breadcrumb.link">Page</Link> </div> )
Breadcrumb
usa el enlace, pero desea poder usarlo en pruebas con data-test-id
más específicos. Hay un defecto en ello.
En la mayoría de los casos, los accesorios personalizados deben tener prioridad sobre los accesorios predeterminados.
En la práctica, esto significa que los accesorios predeterminados deben ir primero, y luego {...props}
para anularlos.
const Link = props => { return ( <a className="link" data-test-id="link" {...props} /> ) }
Recuerde que la segunda aparición de data-test-id
(de los accesorios) anulará la primera (por defecto). Por lo tanto, cuando un desarrollador adjunta su propia data-test-id
o className
, anula la que estaba por defecto:
1. <Link href="sid.studio">Click me</Link> 2. <Link href="sid.studio" data-test-id="breadcrumb.link">Click me</Link> // : 1. <a class="link" href="sid.studio" data-test-id="link">Click me</a> 2. <a class="link" href="sid.studio" data-test-id="breadcrumb.link">Click me</a>
Podemos hacer lo mismo con className
:

<Link href="sid.studio" className="red-link">Click me</Link> // : <a href="sid.studio" class="red-link" data-test-id="link">Click me</a>
Parece bastante extraño, ¡no estoy seguro de que debamos permitir esto! Hablemos más sobre esto.
2. No queremos permitir que el desarrollador cambie algunos accesorios
Supongamos que no queremos que los desarrolladores cambien la apariencia (a través de className
), pero no nos importa que cambien otros accesorios, como id
, data-test-id
, etc.
Podemos hacer esto ordenando el orden de nuestros atributos:
const Link = props => { return ( <a data-test-id="link" {...props} className="link" /> ) }
Recuerde que el atributo de la derecha anulará el atributo de la izquierda. Por lo tanto, todo antes de {...props}
se puede redefinir, pero todo lo que está después no se puede redefinir.
Para simplificar el trabajo de los desarrolladores, puede mostrar una advertencia de que no puede especificar su className
.
Me gusta crear mis propios accesorios de comprobación de tipos para esto:
Link.PropTypes = { className: function(props) { if (props.className) { return new Error( ` className Link, ` ) } } }
Tengo un video que habla sobre la verificación de tipos personalizados de accesorios , en caso de que esté interesado en cómo escribirlos.
Ahora, cuando el desarrollador intenta anular className
, esto no funcionará y recibirá una advertencia.

<Link href="sid.studio" className="red-link">Click me</Link> // : <a href="sid.studio" class="link">Click me</a>
: : className Link,
Honestamente, tuve que usar esta plantilla solo una o dos veces. Por lo general, confía en el desarrollador que usa su componente.
Lo que nos lleva a compartir.
3. El desarrollador debe poder agregar valores mientras mantiene el valor predeterminado
Este es quizás el caso de uso más común para las clases.

<Link href="sid.studio" className="underline">Click me</Link> // : <a href="sid.studio" class="link underline">Click me</a>
La implementación se ve así:
const Link = props => { const { className, otherProps } = props const classes = 'link ' + className return ( <a data-test-id="link" className={classes} {...otherProps} /* */ /> ) }
Esta plantilla también es útil para aceptar controladores de eventos (por ejemplo, onClick
) para un componente que ya los tiene.

<Switch onClick={value => console.log(value)} />
Así es como se ve la implementación de este componente:
class Switch extends React.Component { state = { enabled: false } onToggle = event => { this.setState({ enabled: !this.state.enabled }) if (typeof this.props.onClick === 'function') { this.props.onClick(event, this.state.enabled) } } render() { return <div class="toggler" onClick={this.onToggle} /> } }
Hay otra forma de evitar conflictos de nombres en los controladores de eventos; Lo describí en la API de escritura para React Components, Parte 2: Dar nombres al comportamiento, no a formas de interacción .
Para cada escenario, puede usar diferentes enfoques.
- La mayoría de las veces: el desarrollador debe poder cambiar el valor de la utilería, cuyo valor se estableció de forma predeterminada
- Por lo general, para estilos y controladores de eventos: el desarrollador debe poder agregar un valor sobre el valor predeterminado
- Un caso raro cuando necesita limitar las acciones del desarrollador: el desarrollador no puede cambiar el comportamiento, debe ignorar sus valores y, al mismo tiempo, mostrar advertencias