La réactivité Shiny expliquée : reactive(), observe et eventReactive en R

Comment fonctionnent les mises à jour automatiques — et la boîte à outils réactive qui garde les applications rapides à mesure qu’elles grandissent.

Programming
Shiny

Comprenez le modèle réactif de Shiny : les entrées sont des sources réactives, les sorties des points de sortie réactifs, et le graphe réactif ne réexécute que ce qui dépend de ce qui a changé. Apprenez ensuite la boîte à outils — les expressions reactive(), render vs reactive(), observe()/observeEvent(), eventReactive(), isolate(), et reactiveVal()/reactiveValues() — chacun avec un petit exemple et un « quand l’utiliser » clair.

Date de publication

28 juin 2026

Modifié

7 juillet 2026

AstucePoints clés
  • La réactivité est un graphe, pas du code d’événement. Les entrées sont des sources, les render*()/observateurs sont des points de sortie ; Shiny ne réexécute que les points de sortie qui dépendent de l’entrée qui a changé — vous n’écrivez jamais de gestionnaire « on change ».
  • reactive() construit une valeur réutilisable. Calculez une fois, appelez-la comme une fonction (x()) dans plusieurs sorties ; elle met en cache et ne recalcule que lorsque ses entrées changent.
  • render*() construit une sortie ; reactive() construit une valeur. Utilisez un render pour remplir un emplacement de sortie, un reactive() pour partager un résultat calculé.
  • Les observateurs font des effets de bord, pas des valeurs. observe() réagit à tout changement ; observeEvent(input$go, …) fait X quand un déclencheur se déclenche.
  • Verrouillez et isolez quand vous avez besoin de contrôle. eventReactive() diffère un calcul jusqu’à ce qu’un bouton se déclenche ; isolate() lit une valeur réactive sans en dépendre ; reactiveVal()/reactiveValues() conservent votre propre état modifiable.

Introduction

Dans votre première application vous avez déplacé un curseur et l’histogramme s’est redessiné de lui-même — pas d’actualisation, pas de code d’événement. Cette mise à jour automatique, c’est la réactivité, et jusqu’ici vous n’en avez utilisé que la forme la plus simple : une sortie qui lit une entrée.

Cette leçon explique comment cela fonctionne réellement et vous donne le reste de la boîte à outils. Vous verrez le graphe réactif qui décide de ce qui se réexécute, puis vous découvrirez les fonctions qui vous permettent de réutiliser des valeurs calculées, d’exécuter des effets de bord, de différer un travail coûteux jusqu’à ce qu’un bouton soit cliqué, et de gérer votre propre état. À la fin, vous saurez quel outil utiliser et pourquoi — la différence entre une application qui reste rapide à mesure qu’elle grandit et une qui recalcule tout à chaque frappe.

Chaque exemple est un court extrait de server. Déposez-le dans le server d’une application en cours d’exécution pour l’essayer (voir structure d’une application pour la coquille).

NoteCopiez n’importe quel bloc dans app.R et exécutez-le en local

Le code de cette page n’est pas exécuté ici — une application Shiny a besoin d’une session R active, elle ne peut donc pas tourner dans une page web statique. Copiez n’importe quel bloc dans un fichier app.R et exécutez-le en local avec shiny::runApp() (ou cliquez sur Run App dans RStudio). Chaque extrait se dépose dans la fonction server d’une application.

Le modèle réactif : sources, points de sortie et le graphe

La réactivité comporte trois types de pièces mobiles. Les connaître, c’est tout le modèle mental.

  • Les sources réactives — les entrées. input$bins, input$dataset, une valeur que l’utilisateur change. Elles se trouvent au début de chaque chaîne.
  • Les points de sortie réactifs — les choses qui font quelque chose avec une valeur : les sorties render*() qui remplissent la page, et les observateurs qui provoquent des effets de bord. Ils se trouvent à la fin de chaque chaîne.
  • Les conducteurs réactifs — les expressions reactive() au milieu : elles prennent des sources (ou d’autres conducteurs) et produisent une nouvelle valeur que les points de sortie consomment.

Quand un point de sortie lit une source, Shiny enregistre une dépendance. Le résultat est un graphe réactif : une carte de qui dépend de qui. Quand une entrée change, Shiny parcourt le graphe et réexécute uniquement les points de sortie en aval de cette entrée — rien d’autre. Changez input$bins et le graphique qui le lit se redessine ; un tableau qui ne lit jamais input$bins ne bouge pas.

server <- function(input, output, session) {
  # endpoint reads source -> Shiny links them
  output$distPlot <- renderPlot({
    hist(faithful$waiting, breaks = input$bins)   # depends on input$bins
  })

  # this endpoint never reads input$bins -> the slider does NOT redraw it
  output$count <- renderText({
    paste(nrow(faithful), "eruptions")
  })
}

C’est le contraste avec la programmation ordinaire : vous n’appelez pas le graphique quand le curseur bouge. Vous déclarez que le graphique lit input$bins, et Shiny le réexécute pour vous. Les dépendances sont découvertes automatiquement, à l’exécution, à partir de ce que chaque bloc lit réellement.

reactive() : calculez une fois, réutilisez partout

Quand deux sorties ou plus ont besoin de la même valeur calculée, ne la calculez pas deux fois. Une expression réactive — créée avec reactive() — exécute son code une fois, met le résultat en cache et le donne à quiconque le demande. Elle ne recalcule que lorsque l’une de ses entrées change.

Vous lisez une valeur réactive en l’appelant comme une fonction : selected(). C’est cet appel qui crée la dépendance.

server <- function(input, output, session) {
  # a reactive EXPRESSION: pick the dataset once
  selected <- reactive({
    switch(input$dataset,
           "mtcars"     = mtcars,
           "iris"       = iris,
           "airquality" = airquality)
  })

  # both outputs reuse the SAME cached value
  output$plot <- renderPlot({ plot(selected()) })           # call with ()
  output$rows <- renderText({ paste(nrow(selected()), "rows") })
}

Quand l’utiliser : toute valeur dont plus d’une sortie a besoin, ou toute étape intermédiaire dans une chaîne (charger → nettoyer → analyser). Sans reactive(), mettre ce switch() dans les deux blocs render l’exécuterait deux fois à chaque changement ; avec lui, le travail se fait une fois et les deux sorties partagent le résultat. Les valeurs réactives sont le moyen de rester DRY et rapide en même temps.

NoteAppelez-la avec () — ce n’est pas une simple variable

selected est la valeur réactive ; selected() est sa valeur courante. Oublier les parenthèses (écrire nrow(selected) au lieu de nrow(selected())) est le bug de réactivité le plus courant — voir Problèmes fréquents.

render*() vs reactive() : sortie versus valeur

Ils se ressemblent mais font des travaux différents.

  • render*()renderPlot(), renderText(), renderTable() — construit une sortie et la lie à un emplacement dans le ui (via output$id). Son résultat va à la page ; vous ne l’appelez pas depuis R.
  • reactive() — construit une valeur que vous appelez (x()) et réutilisez dans d’autres valeurs réactives ou renders. Son résultat va à votre code, pas à la page.
server <- function(input, output, session) {
  filtered <- reactive({                 # a VALUE, reused below
    subset(mtcars, mpg >= input$min_mpg)
  })

  output$plot  <- renderPlot({  plot(filtered()$wt, filtered()$mpg) })   # OUTPUT
  output$table <- renderTable({ filtered() })                            # OUTPUT
}

Règle générale : si plus d’une sortie en a besoin, ou si c’est une étape vers une sortie, faites-en un reactive(). Si c’est la chose que l’utilisateur voit, faites-en un render*(). Un render qu’aucune sortie ne lit est du code mort ; un reactive() que rien n’appelle ne s’exécute jamais du tout (c’est l’évaluation paresseuse, ci-dessous).

observe() et observeEvent() : des effets de bord, pas des valeurs

Parfois vous ne voulez pas une valeur — vous voulez une action : écrire dans une base de données, afficher une notification, mettre à jour une autre entrée. C’est un observateur. Les observateurs ne renvoient rien et vous ne les appelez jamais ; ils s’exécutent simplement quand leurs dépendances changent.

# observe(): runs whenever anything it reads changes
observe({
  # keep a dropdown's choices in sync with the chosen dataset
  updateSelectInput(session, "column", choices = names(selected()))
})

observeEvent() est la version ciblée : faire X quand une chose précise se produit. Il surveille un seul déclencheur (généralement un bouton) et ignore tout le reste dans son corps.

# observeEvent(): do X when the button is clicked — and only then
observeEvent(input$save, {
  saveRDS(filtered(), "results.rds")
  showNotification("Saved.", type = "message")
})

Quand utiliser lequel : observe() pour « garder ceci synchronisé avec ce qui a changé » (mises à jour de l’UI, état dérivé). observeEvent(trigger, …) pour « exécuter cette action quand ceci se déclenche » — enregistrer, soumettre, réinitialiser. Le corps d’observeEvent ne réagit qu’à son déclencheur, donc lire d’autres entrées à l’intérieur ne le redéclenchera pas.

eventReactive() : une valeur, calculée seulement quand un déclencheur se déclenche

Un reactive() recalcule à l’instant où l’une de ses entrées change. Pour un travail coûteux — un gros modèle, une requête lente — c’est trop empressé : vous ne voulez pas qu’il se réexécute pendant que l’utilisateur choisit encore ses options. Vous voulez qu’il attende un bouton « Go ».

eventReactive() est exactement cela : une valeur réactive qui ne recalcule que lorsque son déclencheur se déclenche, puis met le résultat en cache comme n’importe quelle valeur réactive.

server <- function(input, output, session) {
  # computes ONLY when input$go is clicked — not when the inputs change
  result <- eventReactive(input$go, {
    expensive_model(input$dataset, input$params)
  })

  output$summary <- renderPrint({ result() })   # shows the last computed result
}

L’utilisateur peut bidouiller input$dataset et input$params autant qu’il veut ; rien ne s’exécute tant qu’il ne clique pas sur Go. Comparez-le à reactive() (empressé, recalcule à chaque changement d’entrée) et à observeEvent() (fait un effet de bord, ne renvoie rien) : eventReactive() est celui qui vous donne une valeur réutilisable et verrouillée.

Quand l’utiliser : tout calcul coûteux que l’utilisateur devrait contrôler — un bouton « Lancer l’analyse », un bouton « Générer un rapport », une requête différée.

isolate() : lire une valeur réactive sans en dépendre

Lire une valeur réactive à l’intérieur d’un point de sortie crée une dépendance — c’est généralement ce que vous voulez. Parfois ce ne l’est pas. Vous pouvez avoir besoin d’une valeur maintenant sans vouloir que ses changements redéclenchent le bloc.

isolate() lit la valeur courante d’une valeur réactive sans en prendre la dépendance.

output$stamp <- renderText({
  count <- input$refresh            # DEPENDS on input$refresh -> re-runs when it changes
  when  <- isolate(Sys.time())      # reads time, but does NOT depend on anything
  paste("Refreshed", count, "times. Last:", when)
})

Ici, la sortie se réexécute quand input$refresh change, et à chaque fois elle saisit l’heure courante — mais elle ne se réexécute jamais à cause de l’heure. Un usage classique est dans observeEvent : réagir au bouton, mais lire les valeurs courantes des autres entrées avec isolate() pour qu’elles n’ajoutent pas leurs propres déclencheurs.

Quand l’utiliser : lire des métadonnées (un horodatage, un champ de note, des infos de session) dont vous voulez la valeur courante sans en faire un déclencheur ; et briser une dépendance qui serait circulaire.

reactiveVal() et reactiveValues() : votre propre état modifiable

Les entrées et les valeurs réactives couvrent la plupart des applications, mais certains états ne sont pas une entrée — un compteur de clics, un total cumulé, un indicateur « à quelle étape sommes-nous ». Pour cela vous créez votre propre valeur réactive que vous pouvez à la fois lire et écrire.

  • reactiveVal() — une valeur. Lisez-la avec x(), définissez-la avec x(new).
  • reactiveValues() — un ensemble nommé de valeurs. Lisez/définissez avec $, comme une liste.
server <- function(input, output, session) {
  # a single reactive value
  count <- reactiveVal(0)

  observeEvent(input$click, {
    count(count() + 1)              # write: call with the new value
  })

  output$n <- renderText({ count() })   # read: call with ()
}
# reactiveValues(): several related pieces of state
state <- reactiveValues(page = 1, selected = character(0))

observeEvent(input$next_page, {
  state$page <- state$page + 1     # read/write with $
})

Quand les utiliser : un état que l’utilisateur construit progressivement plutôt qu’il ne le définit directement — compteurs, sélections qui s’accumulent, étapes d’un assistant, tout ce que vous mettez à jour depuis l’intérieur d’un observateur. Optez pour reactiveVal() pour une seule valeur, reactiveValues() quand plusieurs morceaux d’état vont ensemble.

Évaluation paresseuse : pourquoi les valeurs réactives inutilisées ne s’exécutent pas

Les valeurs réactives sont paresseuses. Un reactive() ne s’exécute pas quand vous le définissez — il s’exécute la première fois que quelque chose l’appelle, pas avant. Si aucune sortie ni aucun observateur ne lit jamais une valeur réactive, son code ne s’exécute jamais du tout.

C’est pourquoi le graphe reste peu coûteux. Shiny ne recalcule pas tout l’univers à chaque changement ; il ne recalcule que les points de sortie qui sont réellement sur la page et en aval de ce qui a changé. Une valeur réactive qui alimente un graphique sur un onglet caché que l’utilisateur n’a pas ouvert ne s’exécute tout simplement pas.

# defined, but it only runs if something calls heavy()
heavy <- reactive({
  Sys.sleep(2)                 # pretend this is slow
  big_computation(input$x)
})

# if NO output ever calls heavy(), this 2-second cost is never paid
output$result <- renderText({ heavy() })   # now it runs — once, cached

Deux conséquences à bien intérioriser : la paresse plus la mise en cache, c’est ce qui rend reactive() libre de réutilisation (le travail se fait une fois, à la demande) ; et c’est pourquoi une valeur réactive qui « ne se met pas à jour » est souvent une valeur réactive que rien n’appelle — voir les problèmes ci-dessous.

Quel outil et quand

Une référence rapide pour toute la boîte à outils — gravez-la dans votre mémoire et la plupart des décisions réactives deviennent évidentes.

Outil Renvoie une valeur ? S’exécute quand… À utiliser quand…
reactive() Oui (appel x()) une entrée qu’il lit change une valeur calculée est partagée par ≥2 sorties / est une étape d’une chaîne
render*() Non (remplit une sortie) une entrée qu’il lit change construire la chose que l’utilisateur voit (graphique, tableau, texte)
observe() Non (effet de bord) tout ce qu’il lit change garder quelque chose synchronisé (mises à jour de l’UI, état dérivé)
observeEvent(x, …) Non (effet de bord) le déclencheur x se déclenche faire une action sur un bouton/événement (enregistrer, soumettre, réinitialiser)
eventReactive(x, …) Oui (appel r()) le déclencheur x se déclenche une valeur coûteuse que l’utilisateur contrôle via un bouton « Go »
isolate(x) la valeur courante de x (aucune dépendance prise) lire une valeur maintenant sans en faire un déclencheur
reactiveVal() Oui (lire x(), écrire x(v)) vous la définissez votre propre valeur modifiable unique (compteur, indicateur)
reactiveValues() Oui (lire/écrire $) vous la définissez plusieurs morceaux d’état modifiable liés

La distinction qui compte le plus : reactive()/eventReactive() vous donnent des valeurs ; observe()/observeEvent() font des effets de bord. Et la distinction au sein de chacune : la version simple réagit à tout ce qu’elle lit ; la version *Event réagit à un seul déclencheur que vous nommez.

Problèmes fréquents

Une valeur réactive « ne se met pas à jour ». Deux causes habituelles. D’abord, vous avez oublié les parenthèses : selected est l’objet réactif, selected() est sa valeur — écrivez nrow(selected()), pas nrow(selected). Ensuite, rien n’appelle la valeur réactive, donc (étant paresseuse) elle ne s’exécute jamais ; assurez-vous qu’une sortie ou un observateur la lit réellement.

Vous avez utilisé observe() là où il fallait reactive(). Si vous voulez une valeur à réutiliser dans plusieurs sorties, c’est un reactive() — il renvoie quelque chose que vous appelez. observe() ne renvoie rien et ne peut pas être appelé, donc il est réservé aux effets de bord. Un symptôme courant : construire une valeur dans observe() puis n’avoir aucun moyen de la lire depuis un render. Déplacez le calcul dans un reactive() et appelez-le.

Le calcul se déclenche trop souvent. Un reactive() coûteux recalcule à chaque changement d’entrée, ce qui peut faire ramer l’application pendant que l’utilisateur choisit encore ses options. Verrouillez-le derrière un bouton avec eventReactive(input$go, { … }) pour qu’il ne s’exécute que quand l’utilisateur le demande — ou utilisez isolate() pour empêcher une entrée précise d’être un déclencheur.

Questions fréquentes

reactive() construit une valeur : il renvoie quelque chose, vous l’appelez avec (), il met en cache, et vous le réutilisez dans plusieurs sorties. observe() réalise un effet de bord : il ne renvoie rien, vous ne l’appelez jamais, et il s’exécute simplement quand ses dépendances changent (mettre à jour une liste déroulante, écrire un fichier, afficher un message). Utilisez reactive() pour les calculs partagés, observe() pour les actions.

Utilisez eventReactive() quand un calcul est coûteux et que l’utilisateur devrait contrôler quand il s’exécute — typiquement derrière un bouton « Lancer » ou « Go ». Un simple reactive() recalcule dès qu’une entrée qu’il lit change, ce qui est trop empressé pour un travail lent ; eventReactive(input$go, { … }) attend le déclencheur, puis met le résultat en cache comme n’importe quelle valeur réactive.

Presque toujours l’une de deux choses. Soit vous l’avez lue sans parenthèses — selected est l’objet réactif, selected() est sa valeur courante, donc utilisez selected(). Soit rien n’appelle la valeur réactive : les valeurs réactives sont paresseuses et ne s’exécutent que lorsqu’une sortie ou un observateur les lit, donc une valeur réactive inutilisée ne s’exécute jamais. Vérifiez qu’un point de sortie l’appelle réellement.

C’est la carte des dépendances que Shiny construit à l’exécution : quelles sorties et quels observateurs lisent quelles entrées et valeurs réactives. Quand une entrée change, Shiny ne réexécute que les parties du graphe en aval de cette entrée — rien d’autre — donc un changement sur un curseur ne redessine que les sorties qui le lisent réellement. Vous ne déclarez jamais le graphe ; il est découvert à partir de ce que chaque bloc lit.

Quand vous avez un état qui n’est pas une entrée mais que vous devez lire et écrire — un compteur de clics, une sélection qui s’accumule, un indicateur « étape courante ». reactiveVal() conserve une telle valeur (x() pour lire, x(v) pour définir) ; reactiveValues() en conserve plusieurs liées (lire/écrire avec $). Vous les mettez généralement à jour depuis l’intérieur d’un observeEvent().

Testez vos connaissances

Une application calcule un résumé lent à partir de deux entrées, et trois sorties en affichent des morceaux. Pour l’instant le code lent est écrit directement à l’intérieur de chaque render, il s’exécute donc trois fois à chaque changement d’entrée. Réécrivez le server pour que le calcul lent (1) s’exécute une seule fois et soit partagé par les trois sorties, et (2) ne s’exécute que quand l’utilisateur clique sur input$go, pas à chaque changement d’entrée.

Deux outils se combinent ici. Pour partager une valeur calculée entre les sorties, vous utiliseriez normalement reactive(). Pour qu’elle attende un bouton au lieu de recalculer à chaque changement, utilisez eventReactive(input$go, { … }) — qui est une valeur réactive (vous l’appelez avec ()) qui ne recalcule que lorsque le déclencheur se déclenche.

server <- function(input, output, session) {

  # computes ONCE per click of input$go, then caches; shared by all outputs
  result <- eventReactive(input$go, {
    slow_summary(input$dataset, input$params)
  })

  output$plot    <- renderPlot({  plot(result()) })
  output$table   <- renderTable({ result()$table })
  output$caption <- renderText({  result()$caption })
}

eventReactive(input$go, …) vous donne une valeur verrouillée et mise en cache : le code lent ne s’exécute que lorsque Go est cliqué, et les trois sorties appellent chacune result() pour réutiliser le même résultat mis en cache — donc le travail se fait une fois par clic au lieu de trois fois par frappe.

Vous calculez un data frame filtré que deux sorties différentes affichent toutes les deux. Quel outil devrait le conserver ?

A. observe(), parce qu’il surveille les entrées. B. reactive(), parce que les deux sorties ont besoin de la même valeur calculée. C. observeEvent(), pour qu’il ne s’exécute que sur un clic de bouton. D. renderTable(), et l’appeler depuis l’autre sortie.

B. Un reactive() renvoie une valeur que vous appelez (filtered()), la calcule une fois, la met en cache, et permet aux deux sorties de la réutiliser. A est faux — observe() fait des effets de bord et ne renvoie rien à réutiliser. C est faux sauf si vous voulez spécifiquement le verrouiller derrière un bouton (la question dit qu’il doit suivre les entrées). D est faux — un render*() remplit un emplacement de sortie et ne peut pas être appelé depuis une autre sortie.

Conclusion

La réactivité est un graphe : les entrées sont des sources, render*() et les observateurs sont des points de sortie, et Shiny ne réexécute que ce qui est en aval du changement. Au-dessus de ce modèle se trouve une boîte à outils petite et précise — reactive() pour les valeurs mises en cache et partagées, render*() pour les sorties, observe()/observeEvent() pour les effets de bord, eventReactive() pour le calcul verrouillé, isolate() pour lire sans dépendre, et reactiveVal()/reactiveValues() pour votre propre état. Associez chacun à son rôle (valeurs vs effets de bord ; empressé vs déclenché) et vos applications restent rapides et prévisibles à mesure qu’elles grandissent.

Cela complète la série Fondations de Shiny : vous savez installer Shiny, structurer une application, en construire une, et désormais raisonner sur la façon dont elle réagit. Ensuite, vous donnerez à vos applications la bonne apparence et le bon ressenti — mises en page, entrées et UI soignée.

Leçons connexes

Cette page vous a-t-elle été utile ?

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {La réactivité Shiny expliquée : reactive(), observe et
    eventReactive en R},
  date = {2026-06-28},
  url = {https://www.datanovia.com/learn/programming/shiny/foundations/reactive-programming},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“La réactivité Shiny expliquée : reactive(), observe et eventReactive en R.” 2026. June 28. https://www.datanovia.com/learn/programming/shiny/foundations/reactive-programming.